From 6aa49b350741d9f64d87d065aabf46a715e8cf7e Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:45:16 +0900 Subject: [PATCH 01/22] Add ForceChangeLockedAccount functionality and related event processing (#1652) --- .../json/IbetSecurityTokenInterface.json | 70 ++++++++++ app/contracts/json/IbetShare.json | 74 +++++++++- app/contracts/json/IbetStraightBond.json | 74 +++++++++- app/model/db/idx_transfer.py | 1 + app/utils/contract_error_code.py | 2 + batch/indexer_Position_Bond.py | 109 ++++++++++++++- batch/indexer_Position_Share.py | 109 ++++++++++++++- batch/indexer_Token_Holders.py | 36 +++++ batch/indexer_Transfer.py | 69 +++++++++ tests/batch/indexer_Position_Bond_test.py | 132 ++++++++++++++++++ tests/batch/indexer_Position_Share_test.py | 127 +++++++++++++++++ tests/batch/indexer_Token_Holders_test.py | 60 +++++--- tests/batch/indexer_Transfer_test.py | 132 +++++++++++++++--- tests/contract_modules.py | 34 +++++ 14 files changed, 984 insertions(+), 45 deletions(-) diff --git a/app/contracts/json/IbetSecurityTokenInterface.json b/app/contracts/json/IbetSecurityTokenInterface.json index 0a993454a..7d607199e 100644 --- a/app/contracts/json/IbetSecurityTokenInterface.json +++ b/app/contracts/json/IbetSecurityTokenInterface.json @@ -182,6 +182,43 @@ "name": "ChangeTransferApprovalRequired", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "beforeAccountAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "afterAccountAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "name": "ForceChangeLockedAccount", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -726,6 +763,39 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_lockAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_beforeAccountAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_afterAccountAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_data", + "type": "string" + } + ], + "name": "forceChangeLockedAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/app/contracts/json/IbetShare.json b/app/contracts/json/IbetShare.json index 5de4bdee5..38fe10266 100644 --- a/app/contracts/json/IbetShare.json +++ b/app/contracts/json/IbetShare.json @@ -264,6 +264,43 @@ "name": "ChangeTransferApprovalRequired", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "beforeAccountAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "afterAccountAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "name": "ForceChangeLockedAccount", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -870,6 +907,39 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_lockAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_beforeAccountAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_afterAccountAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_data", + "type": "string" + } + ], + "name": "forceChangeLockedAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1568,6 +1638,6 @@ "type": "function" } ], - "bytecode": "60806040523480156200001157600080fd5b5060405162003dc838038062003dc88339810160408190526200003491620001d7565b600080546001600160a01b031916331790556001620000548a8262000370565b50600262000063898262000370565b50600080546001600160a01b03191633179055600f879055601281905560038690556014859055601562000098858262000370565b506016620000a7848262000370565b506010620000b6838262000370565b50506013805460ff199081169091556007805490911660011790555050600354600080546001600160a01b031681526008602052604090205550506009805460ff60a01b1916600160a01b179055506200043c92505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200013757600080fd5b81516001600160401b03808211156200015457620001546200010f565b604051601f8301601f19908116603f011681019082821181831017156200017f576200017f6200010f565b81604052838152602092508660208588010111156200019d57600080fd5b600091505b83821015620001c15785820183015181830184015290820190620001a2565b6000602085830101528094505050505092915050565b60008060008060008060008060006101208a8c031215620001f757600080fd5b89516001600160401b03808211156200020f57600080fd5b6200021d8d838e0162000125565b9a5060208c01519150808211156200023457600080fd5b620002428d838e0162000125565b995060408c0151985060608c0151975060808c0151965060a08c01519150808211156200026e57600080fd5b6200027c8d838e0162000125565b955060c08c01519150808211156200029357600080fd5b620002a18d838e0162000125565b945060e08c0151915080821115620002b857600080fd5b50620002c78c828d0162000125565b9250506101008a015190509295985092959850929598565b600181811c90821680620002f457607f821691505b6020821081036200031557634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200036b576000816000526020600020601f850160051c81016020861015620003465750805b601f850160051c820191505b81811015620003675782815560010162000352565b5050505b505050565b81516001600160401b038111156200038c576200038c6200010f565b620003a4816200039d8454620002df565b846200031b565b602080601f831160018114620003dc5760008415620003c35750858301515b600019600386901b1c1916600185901b17855562000367565b600085815260208120601f198616915b828110156200040d57888601518255948401946001909101908401620003ec565b50858210156200042c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61397c806200044c6000396000f3fe608060405234801561001057600080fd5b50600436106103a45760003560e01c806383850bcb116101e9578063c267ce5f1161010f578063e8050052116100ad578063fd0e85691161007c578063fd0e8569146107e3578063fd14a222146107f7578063fef1b9c01461080a578063ff12dcea1461084a57600080fd5b8063e80500521461079d578063edaab208146107b0578063f2fde38b146107bd578063f7502a7c146107d057600080fd5b8063cb761015116100e9578063cb76101514610739578063d93220581461074c578063db20266f1461075f578063e7464db11461078a57600080fd5b8063c267ce5f14610700578063c7109ec914610713578063ca5cbbb51461072657600080fd5b8063996f3a4b11610187578063b2a90a6011610156578063b2a90a60146106b3578063b967a52e146106c7578063b9b3e06a146106da578063bfe30fb0146106ed57600080fd5b8063996f3a4b146106675780639cd237071461067a5780639d8886c81461068d578063a9059cbb146106a057600080fd5b80638ed8a9bc116101c35780638ed8a9bc1461061657806392ff0d31146106295780639301a78b1461063d57806395d89b411461065f57600080fd5b806383850bcb146105f35780638da5cb5b146105fb5780638db9877c1461060e57600080fd5b806340615cf8116102ce5780635c40f6f41161026c57806376fa7a311161023b57806376fa7a31146105a45780637da68d34146105b75780637ed6d103146105c057806381ebdd79146105d357600080fd5b80635c40f6f41461055e5780635ccef3e7146105715780636f3b993f1461058457806370a082311461059157600080fd5b8063428630f6116102a8578063428630f61461052e57806358c3b870146105365780635b4733271461053e5780635c2c20051461055557600080fd5b806340615cf8146104f557806340d1c4411461050857806340eba90e1461051b57600080fd5b8063200d2ed21161034657806327e235e31161031557806327e235e3146104a75780632e027abe146104c757806331cef447146104da57806336f7ab5e146104ed57600080fd5b8063200d2ed21461046157806323b872dd1461046e57806325287d421461048157806325d608611461049457600080fd5b80630ed5a933116103825780630ed5a93314610407578063153a1f3e1461042457806318160ddd146104375780631935a8801461044e57600080fd5b806306eaa0b7146103a957806306fdde03146103be5780630af7eb0f146103dc575b600080fd5b6103bc6103b73660046130c4565b61085d565b005b6103c6610b27565b6040516103d39190613151565b60405180910390f35b6009546103ef906001600160a01b031681565b6040516001600160a01b0390911681526020016103d3565b6013546104149060ff1681565b60405190151581526020016103d3565b6104146104323660046131b0565b610bb5565b61044060035481565b6040519081526020016103d3565b6103bc61045c36600461322d565b610e1d565b6007546104149060ff1681565b61041461047c366004613266565b610eae565b6004546103ef906001600160a01b031681565b6103bc6104a23660046132a2565b611095565b6104406104b53660046132df565b60086020526000908152604090205481565b6103c66104d53660046132fa565b6110ef565b6103bc6104e83660046130c4565b611108565b6103c6611283565b6103bc61050336600461331d565b611290565b6103bc610516366004613347565b61132e565b6103bc6105293660046133af565b61145d565b6103c6611744565b6103c6611751565b61054661175e565b6040516103d393929190613406565b610440600f5481565b6103bc61056c36600461322d565b611883565b6103bc61057f3660046130c4565b611912565b600b546104149060ff1681565b61044061059f3660046132df565b611c94565b6103bc6105b23660046132df565b611caf565b61044060125481565b6103bc6105ce36600461343b565b611d1b565b6104406105e13660046132df565b600d6020526000908152604090205481565b6103bc611e38565b6000546103ef906001600160a01b031681565b6103c6611eba565b6103bc6106243660046132a2565b611ec7565b60095461041490600160a81b900460ff1681565b61065061064b3660046132df565b611f1d565b6040516103d3939291906134d5565b6103c6611f45565b6103bc6106753660046134fd565b611f52565b6103bc61068836600461322d565b611fff565b6103bc61069b36600461343b565b612067565b6104146106ae36600461331d565b61217b565b60095461041490600160b01b900460ff1681565b6103bc6106d53660046132a2565b612242565b6104406106e8366004613560565b612298565b6103bc6106fb366004613347565b6122c3565b6103bc61070e3660046133af565b61239e565b6103bc61072136600461322d565b61247b565b6103bc610734366004613593565b61250e565b6103bc61074736600461360c565b61265d565b6103bc61075a3660046132a2565b6126ac565b61044061076d366004613560565b600e60209081526000928352604080842090915290825290205481565b6103bc610798366004613266565b612702565b6104146107ab36600461343b565b6128d0565b6017546104149060ff1681565b6103bc6107cb3660046132df565b6129f4565b6103bc6107de3660046132df565b612ade565b60095461041490600160a01b900460ff1681565b6103bc61080536600461322d565b612b4a565b61081d61081836600461360c565b612bb2565b604080516001600160a01b03958616815294909316602085015291830152151560608201526080016103d3565b6103bc610858366004613266565b612bfb565b336001600160a01b0316600c838154811061087a5761087a613625565b60009182526020909120600490910201546001600160a01b0316148015906108ad57506000546001600160a01b03163314155b156108ef57604080518082018252600681526531313038303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60405180910390fd5b600c828154811061090257610902613625565b6000918252602082206003600490920201015460ff16151590036109545760408051808201825260068152651898981c181960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b600c828154811061096757610967613625565b90600052602060002090600402016002015460086000600c858154811061099057610990613625565b600091825260208083206004909202909101546001600160a01b03168352820192909252604001812080549091906109c9908490613651565b9091555050600c8054839081106109e2576109e2613625565b906000526020600020906004020160020154600d6000600c8581548110610a0b57610a0b613625565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610a44908490613664565b925050819055506000600c8381548110610a6057610a60613625565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610abd57610abd613625565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610aee57610aee613625565b6000918252602090912060016004909202010154604051610b1b92916001600160a01b0316908690613677565b60405180910390a25050565b60018054610b34906136a3565b80601f0160208091040260200160405190810160405280929190818152602001828054610b60906136a3565b8015610bad5780601f10610b8257610100808354040283529160200191610bad565b820191906000526020600020905b815481529060010190602001808311610b9057829003601f168201915b505050505081565b600b5460009060ff161515600103610bfb57604080518082018252600681526531313035303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b838214610c365760408051808201825260068152651898981a981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b6000805b85811015610c7057848482818110610c5457610c54613625565b9050602002013582610c669190613651565b9150600101610c3a565b5080610c7b33611c94565b1015610cb557604080518082018252600681526531313035303360d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6004546001600160a01b03163314610d16576009546040805180820190915260068152650c4c4c0d4c0d60d21b602082015290600160a81b900460ff161515600114610d145760405162461bcd60e51b81526004016108e69190613151565b505b606060006001935060005b87811015610e1157610d59898983818110610d3e57610d3e613625565b9050602002016020810190610d5391906132df565b3b151590565b15610dae57610da7898983818110610d7357610d73613625565b9050602002016020810190610d8891906132df565b888884818110610d9a57610d9a613625565b9050602002013585612d23565b9150610dfa565b610df7898983818110610dc357610dc3613625565b9050602002016020810190610dd891906132df565b888884818110610dea57610dea613625565b9050602002013585612e3e565b91505b811515600003610e0957600094505b600101610d21565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610e665760405162461bcd60e51b81526004016108e69190613151565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f9060200160405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610ef85760405162461bcd60e51b81526004016108e69190613151565b5081610f0385611c94565b1015610f3d57604080518082018252600681526531313036303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6060833b1561100257610f5983610f5387611c94565b90613002565b6001600160a01b038616600090815260086020526040902055610f8583610f7f86611c94565b90613015565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90610fca903390889087906004016136dd565b600060405180830381600087803b158015610fe457600080fd5b505af1158015610ff8573d6000803e3d6000fd5b505050505061104f565b61100f83610f5387611c94565b6001600160a01b03861660009081526008602052604090205561103583610f7f86611c94565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b03166000805160206139278339815191528560405161108291815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146110de5760405162461bcd60e51b81526004016108e69190613151565b5060116110eb8282613755565b5050565b60186020526000908152604090208054610b34906136a3565b60095460408051808201909152600681526531313130303160d01b602082015290600160b01b900460ff1615156001146111555760405162461bcd60e51b81526004016108e69190613151565b50600954600160a01b900460ff1615156001036112245760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa1580156111be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111e29190613815565b60408051808201909152600681526518989898181960d11b60208201529015156001146112225760405162461bcd60e51b81526004016108e69190613151565b505b336000908152600a602052604090208281556002016112438282613755565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c0338383604051611277939291906136dd565b60405180910390a15050565b60058054610b34906136a3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146112d95760405162461bcd60e51b81526004016108e69190613151565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b19101611277565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113775760405162461bcd60e51b81526004016108e69190613151565b508161138284611c94565b10156113bc57604080518082018252600681526531313136303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6113c982610f5385611c94565b6001600160a01b0384166000908152600860205260409020556113f082610f7f8686612298565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af516029061144f9086908690613832565b60405180910390a350505050565b600b5460ff1615806114795750600954600160a81b900460ff16155b8061148b57508161148933611c94565b105b156114c457604080518082018252600681526531313037303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6000546001600160a01b038481169116148015906114f05750600954600160a01b900460ff1615156001145b156115af5760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015611549573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061156d9190613815565b6040805180820190915260068152651898981b981960d11b60208201529015156001146115ad5760405162461bcd60e51b81526004016108e69190613151565b505b33600090815260086020526040812080548492906115ce908490613664565b9091555050336000908152600d6020526040812080548492906115f2908490613651565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a916117369188908890889061384b565b60405180910390a250505050565b60068054610b34906136a3565b60118054610b34906136a3565b6014805460158054919291611772906136a3565b80601f016020809104026020016040519081016040528092919081815260200182805461179e906136a3565b80156117eb5780601f106117c0576101008083540402835291602001916117eb565b820191906000526020600020905b8154815290600101906020018083116117ce57829003601f168201915b505050505090806002018054611800906136a3565b80601f016020809104026020016040519081016040528092919081815260200182805461182c906136a3565b80156118795780601f1061184e57610100808354040283529160200191611879565b820191906000526020600020905b81548152906001019060200180831161185c57829003601f168201915b5050505050905083565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146118cc5760405162461bcd60e51b81526004016108e69190613151565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461195b5760405162461bcd60e51b81526004016108e69190613151565b50600954600160a81b900460ff1615156000036119a657604080518082018252600681526531313039303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b600c82815481106119b9576119b9613625565b6000918252602082206003600490920201015460ff1615159003611a0b5760408051808201825260068152651898981c981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b600c8281548110611a1e57611a1e613625565b90600052602060002090600402016002015460086000600c8581548110611a4757611a47613625565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611a84908490613651565b9091555050600c805483908110611a9d57611a9d613625565b906000526020600020906004020160020154600d6000600c8581548110611ac657611ac6613625565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611aff908490613664565b925050819055506000600c8381548110611b1b57611b1b613625565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611b7857611b78613625565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611ba957611ba9613625565b6000918252602090912060016004909202010154604051611bd692916001600160a01b0316908690613677565b60405180910390a2600c8281548110611bf157611bf1613625565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c2557611c25613625565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613927833981519152919086908110611c6757611c67613625565b906000526020600020906004020160020154604051611c8891815260200190565b60405180910390a35050565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611cf85760405162461bcd60e51b81526004016108e69190613151565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d645760405162461bcd60e51b81526004016108e69190613151565b508483141580611d745750848114155b15611dad57604080518082018252600681526531313134303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60005b85811015611e2f57611e27878783818110611dcd57611dcd613625565b9050602002016020810190611de291906132df565b868684818110611df457611df4613625565b9050602002016020810190611e0991906132df565b858585818110611e1b57611e1b613625565b90506020020135612702565b600101611db0565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e815760405162461bcd60e51b81526004016108e69190613151565b506013805460ff191660011790556040517f8298c3671093b19970d7c94ce1f23925a962f36fec31d25075d9be072b73e10390600090a1565b60108054610b34906136a3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f105760405162461bcd60e51b81526004016108e69190613151565b5060066110eb8282613755565b600a6020526000908152604090208054600182015460028301805492939192611800906136a3565b60028054610b34906136a3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f9b5760405162461bcd60e51b81526004016108e69190613151565b5060148390556015611fad8382613755565b506016611fba8282613755565b506014546040517ff89b6034f2b759ebb2dfb08461be917a1b54f2e0523c895e7f9b5e5e14dd013e91611ff2916015906016906138fb565b60405180910390a1505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120485760405162461bcd60e51b81526004016108e69190613151565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120b05760405162461bcd60e51b81526004016108e69190613151565b5084831415806120c05750848114155b156120f957604080518082018252600681526531313133303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60005b85811015611e2f5761217387878381811061211957612119613625565b905060200201602081019061212e91906132df565b86868481811061214057612140613625565b905060200201602081019061215591906132df565b85858581811061216757612167613625565b90506020020135612bfb565b6001016120fc565b60008161218733611c94565b10156040518060400160405280600681526020016531313034303160d01b815250906121c65760405162461bcd60e51b81526004016108e69190613151565b506009546040805180820190915260068152651898981a181960d11b602082015290600160a81b900460ff1615156001146122145760405162461bcd60e51b81526004016108e69190613151565b506060833b1561223157612229848483612d23565b91505061223c565b612229848483612e3e565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461228b5760405162461bcd60e51b81526004016108e69190613151565b5060056110eb8282613755565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b816122ce3386612298565b101561230857604080518082018252600681526518989818981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b61231682610f533387612298565b336000908152600e602090815260408083206001600160a01b038916845290915290205561234782610f7f85611c94565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc9061144f908790879087906136dd565b816123a833611c94565b10156123e257604080518082018252600681526518989818181960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b6123ef82610f5333611c94565b33600081815260086020526040902091909155612413908390610f7f908690612298565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f5139061246e9086908690613832565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146124c45760405162461bcd60e51b81526004016108e69190613151565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146125575760405162461bcd60e51b81526004016108e69190613151565b50816125638686612298565b101561259d57604080518082018252600681526531313132303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6125ab82610f538787612298565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556125de82610f7f85611c94565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161264e939291906136dd565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126a65760405162461bcd60e51b81526004016108e69190613151565b50601255565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126f55760405162461bcd60e51b81526004016108e69190613151565b5060106110eb8282613755565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461274b5760405162461bcd60e51b81526004016108e69190613151565b506001600160a01b038216156127e957806127668385612298565b10156127a057604080518082018252600681526531313131303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6127ae81610f538486612298565b6001600160a01b038084166000908152600e60209081526040808320938816835292905220556003546127e19082613002565b600355612874565b6001600160a01b03831660009081526008602052604090205481111561283d57604080518082018252600681526518989898981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b61284a81610f5385611c94565b6001600160a01b0384166000908152600860205260409020556003546128709082613002565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef0846040516128c391815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461291a5760405162461bcd60e51b81526004016108e69190613151565b50858414158061292a5750858214155b1561296357604080518082018252600681526531313135303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60005b868110156129e6576129dd88888381811061298357612983613625565b905060200201602081019061299891906132df565b8787848181106129aa576129aa613625565b90506020020160208101906129bf91906132df565b8686858181106129d1576129d1613625565b90506020020135610eae565b50600101612966565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612a3d5760405162461bcd60e51b81526004016108e69190613151565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612a825760405162461bcd60e51b81526004016108e69190613151565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612b275760405162461bcd60e51b81526004016108e69190613151565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612b935760405162461bcd60e51b81526004016108e69190613151565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c8181548110612bc257600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c445760405162461bcd60e51b81526004016108e69190613151565b506001600160a01b03821615612c9d57612c6281610f7f8486612298565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612c959082613015565b600355612cd4565b612caa81610f7f85611c94565b6001600160a01b038416600090815260086020526040902055600354612cd09082613015565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc846040516128c391815260200190565b60045460408051808201909152600681526531313033303160d01b60208201526000916001600160a01b03868116911614612d715760405162461bcd60e51b81526004016108e69190613151565b50612d7f83610f5333611c94565b33600090815260086020526040902055612d9c83610f7f86611c94565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90612de1903390889088906004016136dd565b600060405180830381600087803b158015612dfb57600080fd5b505af1158015612e0f573d6000803e3d6000fd5b50506040518681526001600160a01b038816925033915060008051602061392783398151915290602001611082565b6004546000906001600160a01b03163314801590612e635750600b5460ff1615156001145b15612e9c57604080518082018252600681526531313032303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6000546001600160a01b03858116911614801590612ec85750600954600160a01b900460ff1615156001145b15612f875760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015612f21573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f459190613815565b60408051808201909152600681526518989819181960d11b6020820152901515600114612f855760405162461bcd60e51b81526004016108e69190613151565b505b612f9483610f5333611c94565b33600090815260086020526040902055612fb183610f7f86611c94565b6001600160a01b03851660008181526008602052604090819020929092559051339060008051602061392783398151915290612ff09087815260200190565b60405180910390a35060019392505050565b600061300e8284613664565b9392505050565b600061300e8284613651565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261304857600080fd5b813567ffffffffffffffff8082111561306357613063613021565b604051601f8301601f19908116603f0116810190828211818310171561308b5761308b613021565b816040528381528660208588010111156130a457600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080604083850312156130d757600080fd5b82359150602083013567ffffffffffffffff8111156130f557600080fd5b61310185828601613037565b9150509250929050565b6000815180845260005b8181101561313157602081850181015186830182015201613115565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061300e602083018461310b565b60008083601f84011261317657600080fd5b50813567ffffffffffffffff81111561318e57600080fd5b6020830191508360208260051b85010111156131a957600080fd5b9250929050565b600080600080604085870312156131c657600080fd5b843567ffffffffffffffff808211156131de57600080fd5b6131ea88838901613164565b9096509450602087013591508082111561320357600080fd5b5061321087828801613164565b95989497509550505050565b801515811461322a57600080fd5b50565b60006020828403121561323f57600080fd5b813561300e8161321c565b80356001600160a01b038116811461326157600080fd5b919050565b60008060006060848603121561327b57600080fd5b6132848461324a565b92506132926020850161324a565b9150604084013590509250925092565b6000602082840312156132b457600080fd5b813567ffffffffffffffff8111156132cb57600080fd5b6132d784828501613037565b949350505050565b6000602082840312156132f157600080fd5b61300e8261324a565b60006020828403121561330c57600080fd5b813560ff8116811461300e57600080fd5b6000806040838503121561333057600080fd5b6133398361324a565b946020939093013593505050565b6000806000806080858703121561335d57600080fd5b6133668561324a565b93506133746020860161324a565b925060408501359150606085013567ffffffffffffffff81111561339757600080fd5b6133a387828801613037565b91505092959194509250565b6000806000606084860312156133c457600080fd5b6133cd8461324a565b925060208401359150604084013567ffffffffffffffff8111156133f057600080fd5b6133fc86828701613037565b9150509250925092565b83815260606020820152600061341f606083018561310b565b8281036040840152613431818561310b565b9695505050505050565b6000806000806000806060878903121561345457600080fd5b863567ffffffffffffffff8082111561346c57600080fd5b6134788a838b01613164565b9098509650602089013591508082111561349157600080fd5b61349d8a838b01613164565b909650945060408901359150808211156134b657600080fd5b506134c389828a01613164565b979a9699509497509295939492505050565b8381528260208201526060604082015260006134f4606083018461310b565b95945050505050565b60008060006060848603121561351257600080fd5b83359250602084013567ffffffffffffffff8082111561353157600080fd5b61353d87838801613037565b9350604086013591508082111561355357600080fd5b506133fc86828701613037565b6000806040838503121561357357600080fd5b61357c8361324a565b915061358a6020840161324a565b90509250929050565b600080600080600060a086880312156135ab57600080fd5b6135b48661324a565b94506135c26020870161324a565b93506135d06040870161324a565b925060608601359150608086013567ffffffffffffffff8111156135f357600080fd5b6135ff88828901613037565b9150509295509295909350565b60006020828403121561361e57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561223c5761223c61363b565b8181038181111561223c5761223c61363b565b6001600160a01b038481168252831660208201526060604082018190526000906134f49083018461310b565b600181811c908216806136b757607f821691505b6020821081036136d757634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b03841681528260208201526060604082015260006134f4606083018461310b565b601f821115613750576000816000526020600020601f850160051c8101602086101561372d5750805b601f850160051c820191505b8181101561374c57828155600101613739565b5050505b505050565b815167ffffffffffffffff81111561376f5761376f613021565b6137838161377d84546136a3565b84613704565b602080601f8311600181146137b857600084156137a05750858301515b600019600386901b1c1916600185901b17855561374c565b600085815260208120601f198616915b828110156137e7578886015182559484019460019091019084016137c8565b50858210156138055787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121561382757600080fd5b815161300e8161321c565b8281526040602082015260006132d7604083018461310b565b6001600160a01b03858116825284166020820152604081018390526080606082018190526000906134319083018461310b565b6000815461388b816136a3565b8085526020600183811680156138a857600181146138c2576138f0565b60ff1985168884015283151560051b8801830195506138f0565b866000528260002060005b858110156138e85781548a82018601529083019084016138cd565b890184019650505b505050505092915050565b838152606060208201526000613914606083018561387e565b8281036040840152613431818561387e56feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220a5f7cb0ed50855b43fe1e45d2da79117e308826e2ea2368e83f41ee188965ca864736f6c63430008170033", - "deployedBytecode": "608060405234801561001057600080fd5b50600436106103a45760003560e01c806383850bcb116101e9578063c267ce5f1161010f578063e8050052116100ad578063fd0e85691161007c578063fd0e8569146107e3578063fd14a222146107f7578063fef1b9c01461080a578063ff12dcea1461084a57600080fd5b8063e80500521461079d578063edaab208146107b0578063f2fde38b146107bd578063f7502a7c146107d057600080fd5b8063cb761015116100e9578063cb76101514610739578063d93220581461074c578063db20266f1461075f578063e7464db11461078a57600080fd5b8063c267ce5f14610700578063c7109ec914610713578063ca5cbbb51461072657600080fd5b8063996f3a4b11610187578063b2a90a6011610156578063b2a90a60146106b3578063b967a52e146106c7578063b9b3e06a146106da578063bfe30fb0146106ed57600080fd5b8063996f3a4b146106675780639cd237071461067a5780639d8886c81461068d578063a9059cbb146106a057600080fd5b80638ed8a9bc116101c35780638ed8a9bc1461061657806392ff0d31146106295780639301a78b1461063d57806395d89b411461065f57600080fd5b806383850bcb146105f35780638da5cb5b146105fb5780638db9877c1461060e57600080fd5b806340615cf8116102ce5780635c40f6f41161026c57806376fa7a311161023b57806376fa7a31146105a45780637da68d34146105b75780637ed6d103146105c057806381ebdd79146105d357600080fd5b80635c40f6f41461055e5780635ccef3e7146105715780636f3b993f1461058457806370a082311461059157600080fd5b8063428630f6116102a8578063428630f61461052e57806358c3b870146105365780635b4733271461053e5780635c2c20051461055557600080fd5b806340615cf8146104f557806340d1c4411461050857806340eba90e1461051b57600080fd5b8063200d2ed21161034657806327e235e31161031557806327e235e3146104a75780632e027abe146104c757806331cef447146104da57806336f7ab5e146104ed57600080fd5b8063200d2ed21461046157806323b872dd1461046e57806325287d421461048157806325d608611461049457600080fd5b80630ed5a933116103825780630ed5a93314610407578063153a1f3e1461042457806318160ddd146104375780631935a8801461044e57600080fd5b806306eaa0b7146103a957806306fdde03146103be5780630af7eb0f146103dc575b600080fd5b6103bc6103b73660046130c4565b61085d565b005b6103c6610b27565b6040516103d39190613151565b60405180910390f35b6009546103ef906001600160a01b031681565b6040516001600160a01b0390911681526020016103d3565b6013546104149060ff1681565b60405190151581526020016103d3565b6104146104323660046131b0565b610bb5565b61044060035481565b6040519081526020016103d3565b6103bc61045c36600461322d565b610e1d565b6007546104149060ff1681565b61041461047c366004613266565b610eae565b6004546103ef906001600160a01b031681565b6103bc6104a23660046132a2565b611095565b6104406104b53660046132df565b60086020526000908152604090205481565b6103c66104d53660046132fa565b6110ef565b6103bc6104e83660046130c4565b611108565b6103c6611283565b6103bc61050336600461331d565b611290565b6103bc610516366004613347565b61132e565b6103bc6105293660046133af565b61145d565b6103c6611744565b6103c6611751565b61054661175e565b6040516103d393929190613406565b610440600f5481565b6103bc61056c36600461322d565b611883565b6103bc61057f3660046130c4565b611912565b600b546104149060ff1681565b61044061059f3660046132df565b611c94565b6103bc6105b23660046132df565b611caf565b61044060125481565b6103bc6105ce36600461343b565b611d1b565b6104406105e13660046132df565b600d6020526000908152604090205481565b6103bc611e38565b6000546103ef906001600160a01b031681565b6103c6611eba565b6103bc6106243660046132a2565b611ec7565b60095461041490600160a81b900460ff1681565b61065061064b3660046132df565b611f1d565b6040516103d3939291906134d5565b6103c6611f45565b6103bc6106753660046134fd565b611f52565b6103bc61068836600461322d565b611fff565b6103bc61069b36600461343b565b612067565b6104146106ae36600461331d565b61217b565b60095461041490600160b01b900460ff1681565b6103bc6106d53660046132a2565b612242565b6104406106e8366004613560565b612298565b6103bc6106fb366004613347565b6122c3565b6103bc61070e3660046133af565b61239e565b6103bc61072136600461322d565b61247b565b6103bc610734366004613593565b61250e565b6103bc61074736600461360c565b61265d565b6103bc61075a3660046132a2565b6126ac565b61044061076d366004613560565b600e60209081526000928352604080842090915290825290205481565b6103bc610798366004613266565b612702565b6104146107ab36600461343b565b6128d0565b6017546104149060ff1681565b6103bc6107cb3660046132df565b6129f4565b6103bc6107de3660046132df565b612ade565b60095461041490600160a01b900460ff1681565b6103bc61080536600461322d565b612b4a565b61081d61081836600461360c565b612bb2565b604080516001600160a01b03958616815294909316602085015291830152151560608201526080016103d3565b6103bc610858366004613266565b612bfb565b336001600160a01b0316600c838154811061087a5761087a613625565b60009182526020909120600490910201546001600160a01b0316148015906108ad57506000546001600160a01b03163314155b156108ef57604080518082018252600681526531313038303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60405180910390fd5b600c828154811061090257610902613625565b6000918252602082206003600490920201015460ff16151590036109545760408051808201825260068152651898981c181960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b600c828154811061096757610967613625565b90600052602060002090600402016002015460086000600c858154811061099057610990613625565b600091825260208083206004909202909101546001600160a01b03168352820192909252604001812080549091906109c9908490613651565b9091555050600c8054839081106109e2576109e2613625565b906000526020600020906004020160020154600d6000600c8581548110610a0b57610a0b613625565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610a44908490613664565b925050819055506000600c8381548110610a6057610a60613625565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610abd57610abd613625565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610aee57610aee613625565b6000918252602090912060016004909202010154604051610b1b92916001600160a01b0316908690613677565b60405180910390a25050565b60018054610b34906136a3565b80601f0160208091040260200160405190810160405280929190818152602001828054610b60906136a3565b8015610bad5780601f10610b8257610100808354040283529160200191610bad565b820191906000526020600020905b815481529060010190602001808311610b9057829003601f168201915b505050505081565b600b5460009060ff161515600103610bfb57604080518082018252600681526531313035303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b838214610c365760408051808201825260068152651898981a981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b6000805b85811015610c7057848482818110610c5457610c54613625565b9050602002013582610c669190613651565b9150600101610c3a565b5080610c7b33611c94565b1015610cb557604080518082018252600681526531313035303360d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6004546001600160a01b03163314610d16576009546040805180820190915260068152650c4c4c0d4c0d60d21b602082015290600160a81b900460ff161515600114610d145760405162461bcd60e51b81526004016108e69190613151565b505b606060006001935060005b87811015610e1157610d59898983818110610d3e57610d3e613625565b9050602002016020810190610d5391906132df565b3b151590565b15610dae57610da7898983818110610d7357610d73613625565b9050602002016020810190610d8891906132df565b888884818110610d9a57610d9a613625565b9050602002013585612d23565b9150610dfa565b610df7898983818110610dc357610dc3613625565b9050602002016020810190610dd891906132df565b888884818110610dea57610dea613625565b9050602002013585612e3e565b91505b811515600003610e0957600094505b600101610d21565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610e665760405162461bcd60e51b81526004016108e69190613151565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f9060200160405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610ef85760405162461bcd60e51b81526004016108e69190613151565b5081610f0385611c94565b1015610f3d57604080518082018252600681526531313036303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6060833b1561100257610f5983610f5387611c94565b90613002565b6001600160a01b038616600090815260086020526040902055610f8583610f7f86611c94565b90613015565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90610fca903390889087906004016136dd565b600060405180830381600087803b158015610fe457600080fd5b505af1158015610ff8573d6000803e3d6000fd5b505050505061104f565b61100f83610f5387611c94565b6001600160a01b03861660009081526008602052604090205561103583610f7f86611c94565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b03166000805160206139278339815191528560405161108291815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146110de5760405162461bcd60e51b81526004016108e69190613151565b5060116110eb8282613755565b5050565b60186020526000908152604090208054610b34906136a3565b60095460408051808201909152600681526531313130303160d01b602082015290600160b01b900460ff1615156001146111555760405162461bcd60e51b81526004016108e69190613151565b50600954600160a01b900460ff1615156001036112245760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa1580156111be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111e29190613815565b60408051808201909152600681526518989898181960d11b60208201529015156001146112225760405162461bcd60e51b81526004016108e69190613151565b505b336000908152600a602052604090208281556002016112438282613755565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c0338383604051611277939291906136dd565b60405180910390a15050565b60058054610b34906136a3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146112d95760405162461bcd60e51b81526004016108e69190613151565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b19101611277565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113775760405162461bcd60e51b81526004016108e69190613151565b508161138284611c94565b10156113bc57604080518082018252600681526531313136303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6113c982610f5385611c94565b6001600160a01b0384166000908152600860205260409020556113f082610f7f8686612298565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af516029061144f9086908690613832565b60405180910390a350505050565b600b5460ff1615806114795750600954600160a81b900460ff16155b8061148b57508161148933611c94565b105b156114c457604080518082018252600681526531313037303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6000546001600160a01b038481169116148015906114f05750600954600160a01b900460ff1615156001145b156115af5760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015611549573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061156d9190613815565b6040805180820190915260068152651898981b981960d11b60208201529015156001146115ad5760405162461bcd60e51b81526004016108e69190613151565b505b33600090815260086020526040812080548492906115ce908490613664565b9091555050336000908152600d6020526040812080548492906115f2908490613651565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a916117369188908890889061384b565b60405180910390a250505050565b60068054610b34906136a3565b60118054610b34906136a3565b6014805460158054919291611772906136a3565b80601f016020809104026020016040519081016040528092919081815260200182805461179e906136a3565b80156117eb5780601f106117c0576101008083540402835291602001916117eb565b820191906000526020600020905b8154815290600101906020018083116117ce57829003601f168201915b505050505090806002018054611800906136a3565b80601f016020809104026020016040519081016040528092919081815260200182805461182c906136a3565b80156118795780601f1061184e57610100808354040283529160200191611879565b820191906000526020600020905b81548152906001019060200180831161185c57829003601f168201915b5050505050905083565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146118cc5760405162461bcd60e51b81526004016108e69190613151565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461195b5760405162461bcd60e51b81526004016108e69190613151565b50600954600160a81b900460ff1615156000036119a657604080518082018252600681526531313039303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b600c82815481106119b9576119b9613625565b6000918252602082206003600490920201015460ff1615159003611a0b5760408051808201825260068152651898981c981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b600c8281548110611a1e57611a1e613625565b90600052602060002090600402016002015460086000600c8581548110611a4757611a47613625565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611a84908490613651565b9091555050600c805483908110611a9d57611a9d613625565b906000526020600020906004020160020154600d6000600c8581548110611ac657611ac6613625565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611aff908490613664565b925050819055506000600c8381548110611b1b57611b1b613625565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611b7857611b78613625565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611ba957611ba9613625565b6000918252602090912060016004909202010154604051611bd692916001600160a01b0316908690613677565b60405180910390a2600c8281548110611bf157611bf1613625565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c2557611c25613625565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613927833981519152919086908110611c6757611c67613625565b906000526020600020906004020160020154604051611c8891815260200190565b60405180910390a35050565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611cf85760405162461bcd60e51b81526004016108e69190613151565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d645760405162461bcd60e51b81526004016108e69190613151565b508483141580611d745750848114155b15611dad57604080518082018252600681526531313134303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60005b85811015611e2f57611e27878783818110611dcd57611dcd613625565b9050602002016020810190611de291906132df565b868684818110611df457611df4613625565b9050602002016020810190611e0991906132df565b858585818110611e1b57611e1b613625565b90506020020135612702565b600101611db0565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e815760405162461bcd60e51b81526004016108e69190613151565b506013805460ff191660011790556040517f8298c3671093b19970d7c94ce1f23925a962f36fec31d25075d9be072b73e10390600090a1565b60108054610b34906136a3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f105760405162461bcd60e51b81526004016108e69190613151565b5060066110eb8282613755565b600a6020526000908152604090208054600182015460028301805492939192611800906136a3565b60028054610b34906136a3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f9b5760405162461bcd60e51b81526004016108e69190613151565b5060148390556015611fad8382613755565b506016611fba8282613755565b506014546040517ff89b6034f2b759ebb2dfb08461be917a1b54f2e0523c895e7f9b5e5e14dd013e91611ff2916015906016906138fb565b60405180910390a1505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120485760405162461bcd60e51b81526004016108e69190613151565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120b05760405162461bcd60e51b81526004016108e69190613151565b5084831415806120c05750848114155b156120f957604080518082018252600681526531313133303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60005b85811015611e2f5761217387878381811061211957612119613625565b905060200201602081019061212e91906132df565b86868481811061214057612140613625565b905060200201602081019061215591906132df565b85858581811061216757612167613625565b90506020020135612bfb565b6001016120fc565b60008161218733611c94565b10156040518060400160405280600681526020016531313034303160d01b815250906121c65760405162461bcd60e51b81526004016108e69190613151565b506009546040805180820190915260068152651898981a181960d11b602082015290600160a81b900460ff1615156001146122145760405162461bcd60e51b81526004016108e69190613151565b506060833b1561223157612229848483612d23565b91505061223c565b612229848483612e3e565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461228b5760405162461bcd60e51b81526004016108e69190613151565b5060056110eb8282613755565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b816122ce3386612298565b101561230857604080518082018252600681526518989818981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b61231682610f533387612298565b336000908152600e602090815260408083206001600160a01b038916845290915290205561234782610f7f85611c94565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc9061144f908790879087906136dd565b816123a833611c94565b10156123e257604080518082018252600681526518989818181960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b6123ef82610f5333611c94565b33600081815260086020526040902091909155612413908390610f7f908690612298565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f5139061246e9086908690613832565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146124c45760405162461bcd60e51b81526004016108e69190613151565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146125575760405162461bcd60e51b81526004016108e69190613151565b50816125638686612298565b101561259d57604080518082018252600681526531313132303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6125ab82610f538787612298565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556125de82610f7f85611c94565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161264e939291906136dd565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126a65760405162461bcd60e51b81526004016108e69190613151565b50601255565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126f55760405162461bcd60e51b81526004016108e69190613151565b5060106110eb8282613755565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461274b5760405162461bcd60e51b81526004016108e69190613151565b506001600160a01b038216156127e957806127668385612298565b10156127a057604080518082018252600681526531313131303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6127ae81610f538486612298565b6001600160a01b038084166000908152600e60209081526040808320938816835292905220556003546127e19082613002565b600355612874565b6001600160a01b03831660009081526008602052604090205481111561283d57604080518082018252600681526518989898981960d11b6020820152905162461bcd60e51b81526108e69190600401613151565b61284a81610f5385611c94565b6001600160a01b0384166000908152600860205260409020556003546128709082613002565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef0846040516128c391815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461291a5760405162461bcd60e51b81526004016108e69190613151565b50858414158061292a5750858214155b1561296357604080518082018252600681526531313135303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b60005b868110156129e6576129dd88888381811061298357612983613625565b905060200201602081019061299891906132df565b8787848181106129aa576129aa613625565b90506020020160208101906129bf91906132df565b8686858181106129d1576129d1613625565b90506020020135610eae565b50600101612966565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612a3d5760405162461bcd60e51b81526004016108e69190613151565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612a825760405162461bcd60e51b81526004016108e69190613151565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612b275760405162461bcd60e51b81526004016108e69190613151565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612b935760405162461bcd60e51b81526004016108e69190613151565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c8181548110612bc257600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c445760405162461bcd60e51b81526004016108e69190613151565b506001600160a01b03821615612c9d57612c6281610f7f8486612298565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612c959082613015565b600355612cd4565b612caa81610f7f85611c94565b6001600160a01b038416600090815260086020526040902055600354612cd09082613015565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc846040516128c391815260200190565b60045460408051808201909152600681526531313033303160d01b60208201526000916001600160a01b03868116911614612d715760405162461bcd60e51b81526004016108e69190613151565b50612d7f83610f5333611c94565b33600090815260086020526040902055612d9c83610f7f86611c94565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90612de1903390889088906004016136dd565b600060405180830381600087803b158015612dfb57600080fd5b505af1158015612e0f573d6000803e3d6000fd5b50506040518681526001600160a01b038816925033915060008051602061392783398151915290602001611082565b6004546000906001600160a01b03163314801590612e635750600b5460ff1615156001145b15612e9c57604080518082018252600681526531313032303160d01b6020820152905162461bcd60e51b81526108e69190600401613151565b6000546001600160a01b03858116911614801590612ec85750600954600160a01b900460ff1615156001145b15612f875760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015612f21573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f459190613815565b60408051808201909152600681526518989819181960d11b6020820152901515600114612f855760405162461bcd60e51b81526004016108e69190613151565b505b612f9483610f5333611c94565b33600090815260086020526040902055612fb183610f7f86611c94565b6001600160a01b03851660008181526008602052604090819020929092559051339060008051602061392783398151915290612ff09087815260200190565b60405180910390a35060019392505050565b600061300e8284613664565b9392505050565b600061300e8284613651565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261304857600080fd5b813567ffffffffffffffff8082111561306357613063613021565b604051601f8301601f19908116603f0116810190828211818310171561308b5761308b613021565b816040528381528660208588010111156130a457600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080604083850312156130d757600080fd5b82359150602083013567ffffffffffffffff8111156130f557600080fd5b61310185828601613037565b9150509250929050565b6000815180845260005b8181101561313157602081850181015186830182015201613115565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061300e602083018461310b565b60008083601f84011261317657600080fd5b50813567ffffffffffffffff81111561318e57600080fd5b6020830191508360208260051b85010111156131a957600080fd5b9250929050565b600080600080604085870312156131c657600080fd5b843567ffffffffffffffff808211156131de57600080fd5b6131ea88838901613164565b9096509450602087013591508082111561320357600080fd5b5061321087828801613164565b95989497509550505050565b801515811461322a57600080fd5b50565b60006020828403121561323f57600080fd5b813561300e8161321c565b80356001600160a01b038116811461326157600080fd5b919050565b60008060006060848603121561327b57600080fd5b6132848461324a565b92506132926020850161324a565b9150604084013590509250925092565b6000602082840312156132b457600080fd5b813567ffffffffffffffff8111156132cb57600080fd5b6132d784828501613037565b949350505050565b6000602082840312156132f157600080fd5b61300e8261324a565b60006020828403121561330c57600080fd5b813560ff8116811461300e57600080fd5b6000806040838503121561333057600080fd5b6133398361324a565b946020939093013593505050565b6000806000806080858703121561335d57600080fd5b6133668561324a565b93506133746020860161324a565b925060408501359150606085013567ffffffffffffffff81111561339757600080fd5b6133a387828801613037565b91505092959194509250565b6000806000606084860312156133c457600080fd5b6133cd8461324a565b925060208401359150604084013567ffffffffffffffff8111156133f057600080fd5b6133fc86828701613037565b9150509250925092565b83815260606020820152600061341f606083018561310b565b8281036040840152613431818561310b565b9695505050505050565b6000806000806000806060878903121561345457600080fd5b863567ffffffffffffffff8082111561346c57600080fd5b6134788a838b01613164565b9098509650602089013591508082111561349157600080fd5b61349d8a838b01613164565b909650945060408901359150808211156134b657600080fd5b506134c389828a01613164565b979a9699509497509295939492505050565b8381528260208201526060604082015260006134f4606083018461310b565b95945050505050565b60008060006060848603121561351257600080fd5b83359250602084013567ffffffffffffffff8082111561353157600080fd5b61353d87838801613037565b9350604086013591508082111561355357600080fd5b506133fc86828701613037565b6000806040838503121561357357600080fd5b61357c8361324a565b915061358a6020840161324a565b90509250929050565b600080600080600060a086880312156135ab57600080fd5b6135b48661324a565b94506135c26020870161324a565b93506135d06040870161324a565b925060608601359150608086013567ffffffffffffffff8111156135f357600080fd5b6135ff88828901613037565b9150509295509295909350565b60006020828403121561361e57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561223c5761223c61363b565b8181038181111561223c5761223c61363b565b6001600160a01b038481168252831660208201526060604082018190526000906134f49083018461310b565b600181811c908216806136b757607f821691505b6020821081036136d757634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b03841681528260208201526060604082015260006134f4606083018461310b565b601f821115613750576000816000526020600020601f850160051c8101602086101561372d5750805b601f850160051c820191505b8181101561374c57828155600101613739565b5050505b505050565b815167ffffffffffffffff81111561376f5761376f613021565b6137838161377d84546136a3565b84613704565b602080601f8311600181146137b857600084156137a05750858301515b600019600386901b1c1916600185901b17855561374c565b600085815260208120601f198616915b828110156137e7578886015182559484019460019091019084016137c8565b50858210156138055787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121561382757600080fd5b815161300e8161321c565b8281526040602082015260006132d7604083018461310b565b6001600160a01b03858116825284166020820152604081018390526080606082018190526000906134319083018461310b565b6000815461388b816136a3565b8085526020600183811680156138a857600181146138c2576138f0565b60ff1985168884015283151560051b8801830195506138f0565b866000528260002060005b858110156138e85781548a82018601529083019084016138cd565b890184019650505b505050505092915050565b838152606060208201526000613914606083018561387e565b8281036040840152613431818561387e56feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220a5f7cb0ed50855b43fe1e45d2da79117e308826e2ea2368e83f41ee188965ca864736f6c63430008170033" + "bytecode": "60806040523480156200001157600080fd5b5060405162003f2638038062003f268339810160408190526200003491620001d7565b600080546001600160a01b031916331790556001620000548a8262000370565b50600262000063898262000370565b50600080546001600160a01b03191633179055600f879055601281905560038690556014859055601562000098858262000370565b506016620000a7848262000370565b506010620000b6838262000370565b50506013805460ff199081169091556007805490911660011790555050600354600080546001600160a01b031681526008602052604090205550506009805460ff60a01b1916600160a01b179055506200043c92505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200013757600080fd5b81516001600160401b03808211156200015457620001546200010f565b604051601f8301601f19908116603f011681019082821181831017156200017f576200017f6200010f565b81604052838152602092508660208588010111156200019d57600080fd5b600091505b83821015620001c15785820183015181830184015290820190620001a2565b6000602085830101528094505050505092915050565b60008060008060008060008060006101208a8c031215620001f757600080fd5b89516001600160401b03808211156200020f57600080fd5b6200021d8d838e0162000125565b9a5060208c01519150808211156200023457600080fd5b620002428d838e0162000125565b995060408c0151985060608c0151975060808c0151965060a08c01519150808211156200026e57600080fd5b6200027c8d838e0162000125565b955060c08c01519150808211156200029357600080fd5b620002a18d838e0162000125565b945060e08c0151915080821115620002b857600080fd5b50620002c78c828d0162000125565b9250506101008a015190509295985092959850929598565b600181811c90821680620002f457607f821691505b6020821081036200031557634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200036b576000816000526020600020601f850160051c81016020861015620003465750805b601f850160051c820191505b81811015620003675782815560010162000352565b5050505b505050565b81516001600160401b038111156200038c576200038c6200010f565b620003a4816200039d8454620002df565b846200031b565b602080601f831160018114620003dc5760008415620003c35750858301515b600019600386901b1c1916600185901b17855562000367565b600085815260208120601f198616915b828110156200040d57888601518255948401946001909101908401620003ec565b50858210156200042c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b613ada806200044c6000396000f3fe608060405234801561001057600080fd5b50600436106103af5760003560e01c806383850bcb116101f4578063c267ce5f1161011a578063e8050052116100ad578063fd0e85691161007c578063fd0e856914610801578063fd14a22214610815578063fef1b9c014610828578063ff12dcea1461086857600080fd5b8063e8050052146107bb578063edaab208146107ce578063f2fde38b146107db578063f7502a7c146107ee57600080fd5b8063d87f0986116100e9578063d87f098614610757578063d93220581461076a578063db20266f1461077d578063e7464db1146107a857600080fd5b8063c267ce5f1461070b578063c7109ec91461071e578063ca5cbbb514610731578063cb7610151461074457600080fd5b8063996f3a4b11610192578063b2a90a6011610161578063b2a90a60146106be578063b967a52e146106d2578063b9b3e06a146106e5578063bfe30fb0146106f857600080fd5b8063996f3a4b146106725780639cd23707146106855780639d8886c814610698578063a9059cbb146106ab57600080fd5b80638ed8a9bc116101ce5780638ed8a9bc1461062157806392ff0d31146106345780639301a78b1461064857806395d89b411461066a57600080fd5b806383850bcb146105fe5780638da5cb5b146106065780638db9877c1461061957600080fd5b806340615cf8116102d95780635c40f6f41161027757806376fa7a311161024657806376fa7a31146105af5780637da68d34146105c25780637ed6d103146105cb57806381ebdd79146105de57600080fd5b80635c40f6f4146105695780635ccef3e71461057c5780636f3b993f1461058f57806370a082311461059c57600080fd5b8063428630f6116102b3578063428630f61461053957806358c3b870146105415780635b473327146105495780635c2c20051461056057600080fd5b806340615cf81461050057806340d1c4411461051357806340eba90e1461052657600080fd5b8063200d2ed21161035157806327e235e31161032057806327e235e3146104b25780632e027abe146104d257806331cef447146104e557806336f7ab5e146104f857600080fd5b8063200d2ed21461046c57806323b872dd1461047957806325287d421461048c57806325d608611461049f57600080fd5b80630ed5a9331161038d5780630ed5a93314610412578063153a1f3e1461042f57806318160ddd146104425780631935a8801461045957600080fd5b806306eaa0b7146103b457806306fdde03146103c95780630af7eb0f146103e7575b600080fd5b6103c76103c2366004613222565b61087b565b005b6103d1610b45565b6040516103de91906132af565b60405180910390f35b6009546103fa906001600160a01b031681565b6040516001600160a01b0390911681526020016103de565b60135461041f9060ff1681565b60405190151581526020016103de565b61041f61043d36600461330e565b610bd3565b61044b60035481565b6040519081526020016103de565b6103c761046736600461338b565b610e3b565b60075461041f9060ff1681565b61041f6104873660046133c4565b610ecc565b6004546103fa906001600160a01b031681565b6103c76104ad366004613400565b6110b3565b61044b6104c036600461343d565b60086020526000908152604090205481565b6103d16104e0366004613458565b61110d565b6103c76104f3366004613222565b611126565b6103d16112a1565b6103c761050e36600461347b565b6112ae565b6103c76105213660046134a5565b61134c565b6103c761053436600461350d565b61147b565b6103d1611762565b6103d161176f565b61055161177c565b6040516103de93929190613564565b61044b600f5481565b6103c761057736600461338b565b6118a1565b6103c761058a366004613222565b611930565b600b5461041f9060ff1681565b61044b6105aa36600461343d565b611cb2565b6103c76105bd36600461343d565b611ccd565b61044b60125481565b6103c76105d9366004613599565b611d39565b61044b6105ec36600461343d565b600d6020526000908152604090205481565b6103c7611e56565b6000546103fa906001600160a01b031681565b6103d1611ed8565b6103c761062f366004613400565b611ee5565b60095461041f90600160a81b900460ff1681565b61065b61065636600461343d565b611f3b565b6040516103de93929190613633565b6103d1611f63565b6103c761068036600461365b565b611f70565b6103c761069336600461338b565b61201d565b6103c76106a6366004613599565b612085565b61041f6106b936600461347b565b612199565b60095461041f90600160b01b900460ff1681565b6103c76106e0366004613400565b612260565b61044b6106f33660046136be565b6122b6565b6103c76107063660046134a5565b6122e1565b6103c761071936600461350d565b6123bc565b6103c761072c36600461338b565b612499565b6103c761073f3660046136f1565b61252c565b6103c761075236600461376a565b61267b565b6103c76107653660046136f1565b6126ca565b6103c7610778366004613400565b61280a565b61044b61078b3660046136be565b600e60209081526000928352604080842090915290825290205481565b6103c76107b63660046133c4565b612860565b61041f6107c9366004613599565b612a2e565b60175461041f9060ff1681565b6103c76107e936600461343d565b612b52565b6103c76107fc36600461343d565b612c3c565b60095461041f90600160a01b900460ff1681565b6103c761082336600461338b565b612ca8565b61083b61083636600461376a565b612d10565b604080516001600160a01b03958616815294909316602085015291830152151560608201526080016103de565b6103c76108763660046133c4565b612d59565b336001600160a01b0316600c838154811061089857610898613783565b60009182526020909120600490910201546001600160a01b0316148015906108cb57506000546001600160a01b03163314155b1561090d57604080518082018252600681526531313038303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60405180910390fd5b600c828154811061092057610920613783565b6000918252602082206003600490920201015460ff16151590036109725760408051808201825260068152651898981c181960d11b6020820152905162461bcd60e51b815261090491906004016132af565b600c828154811061098557610985613783565b90600052602060002090600402016002015460086000600c85815481106109ae576109ae613783565b600091825260208083206004909202909101546001600160a01b03168352820192909252604001812080549091906109e79084906137af565b9091555050600c805483908110610a0057610a00613783565b906000526020600020906004020160020154600d6000600c8581548110610a2957610a29613783565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610a629084906137c2565b925050819055506000600c8381548110610a7e57610a7e613783565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610adb57610adb613783565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610b0c57610b0c613783565b6000918252602090912060016004909202010154604051610b3992916001600160a01b03169086906137d5565b60405180910390a25050565b60018054610b5290613801565b80601f0160208091040260200160405190810160405280929190818152602001828054610b7e90613801565b8015610bcb5780601f10610ba057610100808354040283529160200191610bcb565b820191906000526020600020905b815481529060010190602001808311610bae57829003601f168201915b505050505081565b600b5460009060ff161515600103610c1957604080518082018252600681526531313035303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b838214610c545760408051808201825260068152651898981a981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b6000805b85811015610c8e57848482818110610c7257610c72613783565b9050602002013582610c8491906137af565b9150600101610c58565b5080610c9933611cb2565b1015610cd357604080518082018252600681526531313035303360d01b6020820152905162461bcd60e51b815261090491906004016132af565b6004546001600160a01b03163314610d34576009546040805180820190915260068152650c4c4c0d4c0d60d21b602082015290600160a81b900460ff161515600114610d325760405162461bcd60e51b815260040161090491906132af565b505b606060006001935060005b87811015610e2f57610d77898983818110610d5c57610d5c613783565b9050602002016020810190610d71919061343d565b3b151590565b15610dcc57610dc5898983818110610d9157610d91613783565b9050602002016020810190610da6919061343d565b888884818110610db857610db8613783565b9050602002013585612e81565b9150610e18565b610e15898983818110610de157610de1613783565b9050602002016020810190610df6919061343d565b888884818110610e0857610e08613783565b9050602002013585612f9c565b91505b811515600003610e2757600094505b600101610d3f565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610e845760405162461bcd60e51b815260040161090491906132af565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f9060200160405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610f165760405162461bcd60e51b815260040161090491906132af565b5081610f2185611cb2565b1015610f5b57604080518082018252600681526531313036303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6060833b1561102057610f7783610f7187611cb2565b90613160565b6001600160a01b038616600090815260086020526040902055610fa383610f9d86611cb2565b90613173565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90610fe89033908890879060040161383b565b600060405180830381600087803b15801561100257600080fd5b505af1158015611016573d6000803e3d6000fd5b505050505061106d565b61102d83610f7187611cb2565b6001600160a01b03861660009081526008602052604090205561105383610f9d86611cb2565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b0316600080516020613a85833981519152856040516110a091815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146110fc5760405162461bcd60e51b815260040161090491906132af565b50601161110982826138b3565b5050565b60186020526000908152604090208054610b5290613801565b60095460408051808201909152600681526531313130303160d01b602082015290600160b01b900460ff1615156001146111735760405162461bcd60e51b815260040161090491906132af565b50600954600160a01b900460ff1615156001036112425760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa1580156111dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112009190613973565b60408051808201909152600681526518989898181960d11b60208201529015156001146112405760405162461bcd60e51b815260040161090491906132af565b505b336000908152600a6020526040902082815560020161126182826138b3565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c03383836040516112959392919061383b565b60405180910390a15050565b60058054610b5290613801565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146112f75760405162461bcd60e51b815260040161090491906132af565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b19101611295565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113955760405162461bcd60e51b815260040161090491906132af565b50816113a084611cb2565b10156113da57604080518082018252600681526531313136303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6113e782610f7185611cb2565b6001600160a01b03841660009081526008602052604090205561140e82610f9d86866122b6565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af516029061146d9086908690613990565b60405180910390a350505050565b600b5460ff1615806114975750600954600160a81b900460ff16155b806114a95750816114a733611cb2565b105b156114e257604080518082018252600681526531313037303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6000546001600160a01b0384811691161480159061150e5750600954600160a01b900460ff1615156001145b156115cd5760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015611567573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061158b9190613973565b6040805180820190915260068152651898981b981960d11b60208201529015156001146115cb5760405162461bcd60e51b815260040161090491906132af565b505b33600090815260086020526040812080548492906115ec9084906137c2565b9091555050336000908152600d6020526040812080548492906116109084906137af565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a91611754918890889088906139a9565b60405180910390a250505050565b60068054610b5290613801565b60118054610b5290613801565b601480546015805491929161179090613801565b80601f01602080910402602001604051908101604052809291908181526020018280546117bc90613801565b80156118095780601f106117de57610100808354040283529160200191611809565b820191906000526020600020905b8154815290600101906020018083116117ec57829003601f168201915b50505050509080600201805461181e90613801565b80601f016020809104026020016040519081016040528092919081815260200182805461184a90613801565b80156118975780601f1061186c57610100808354040283529160200191611897565b820191906000526020600020905b81548152906001019060200180831161187a57829003601f168201915b5050505050905083565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146118ea5760405162461bcd60e51b815260040161090491906132af565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119795760405162461bcd60e51b815260040161090491906132af565b50600954600160a81b900460ff1615156000036119c457604080518082018252600681526531313039303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b600c82815481106119d7576119d7613783565b6000918252602082206003600490920201015460ff1615159003611a295760408051808201825260068152651898981c981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b600c8281548110611a3c57611a3c613783565b90600052602060002090600402016002015460086000600c8581548110611a6557611a65613783565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611aa29084906137af565b9091555050600c805483908110611abb57611abb613783565b906000526020600020906004020160020154600d6000600c8581548110611ae457611ae4613783565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611b1d9084906137c2565b925050819055506000600c8381548110611b3957611b39613783565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611b9657611b96613783565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611bc757611bc7613783565b6000918252602090912060016004909202010154604051611bf492916001600160a01b03169086906137d5565b60405180910390a2600c8281548110611c0f57611c0f613783565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c4357611c43613783565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613a85833981519152919086908110611c8557611c85613783565b906000526020600020906004020160020154604051611ca691815260200190565b60405180910390a35050565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d165760405162461bcd60e51b815260040161090491906132af565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d825760405162461bcd60e51b815260040161090491906132af565b508483141580611d925750848114155b15611dcb57604080518082018252600681526531313134303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60005b85811015611e4d57611e45878783818110611deb57611deb613783565b9050602002016020810190611e00919061343d565b868684818110611e1257611e12613783565b9050602002016020810190611e27919061343d565b858585818110611e3957611e39613783565b90506020020135612860565b600101611dce565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e9f5760405162461bcd60e51b815260040161090491906132af565b506013805460ff191660011790556040517f8298c3671093b19970d7c94ce1f23925a962f36fec31d25075d9be072b73e10390600090a1565b60108054610b5290613801565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f2e5760405162461bcd60e51b815260040161090491906132af565b50600661110982826138b3565b600a602052600090815260409020805460018201546002830180549293919261181e90613801565b60028054610b5290613801565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611fb95760405162461bcd60e51b815260040161090491906132af565b5060148390556015611fcb83826138b3565b506016611fd882826138b3565b506014546040517ff89b6034f2b759ebb2dfb08461be917a1b54f2e0523c895e7f9b5e5e14dd013e9161201091601590601690613a59565b60405180910390a1505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120665760405162461bcd60e51b815260040161090491906132af565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120ce5760405162461bcd60e51b815260040161090491906132af565b5084831415806120de5750848114155b1561211757604080518082018252600681526531313133303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60005b85811015611e4d5761219187878381811061213757612137613783565b905060200201602081019061214c919061343d565b86868481811061215e5761215e613783565b9050602002016020810190612173919061343d565b85858581811061218557612185613783565b90506020020135612d59565b60010161211a565b6000816121a533611cb2565b10156040518060400160405280600681526020016531313034303160d01b815250906121e45760405162461bcd60e51b815260040161090491906132af565b506009546040805180820190915260068152651898981a181960d11b602082015290600160a81b900460ff1615156001146122325760405162461bcd60e51b815260040161090491906132af565b506060833b1561224f57612247848483612e81565b91505061225a565b612247848483612f9c565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146122a95760405162461bcd60e51b815260040161090491906132af565b50600561110982826138b3565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b816122ec33866122b6565b101561232657604080518082018252600681526518989818981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b61233482610f7133876122b6565b336000908152600e602090815260408083206001600160a01b038916845290915290205561236582610f9d85611cb2565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc9061146d9087908790879061383b565b816123c633611cb2565b101561240057604080518082018252600681526518989818181960d11b6020820152905162461bcd60e51b815261090491906004016132af565b61240d82610f7133611cb2565b33600081815260086020526040902091909155612431908390610f9d9086906122b6565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f5139061248c9086908690613990565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146124e25760405162461bcd60e51b815260040161090491906132af565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146125755760405162461bcd60e51b815260040161090491906132af565b508161258186866122b6565b10156125bb57604080518082018252600681526531313132303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6125c982610f7187876122b6565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556125fc82610f9d85611cb2565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161266c9392919061383b565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126c45760405162461bcd60e51b815260040161090491906132af565b50601255565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146127135760405162461bcd60e51b815260040161090491906132af565b508161271f86866122b6565b101561275957604080518082018252600681526531313137303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b61276782610f7187876122b6565b6001600160a01b038087166000908152600e602090815260408083209389168352929052205561279b82610f9d87866122b6565b6001600160a01b038087166000818152600e602090815260408083208986168085529252918290209490945551918716917f5847ffcc1bce0ecfdd115863db8562df2b18b61698938e251adf381774e9e3be906127fb9087908790613990565b60405180910390a45050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128535760405162461bcd60e51b815260040161090491906132af565b50601061110982826138b3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128a95760405162461bcd60e51b815260040161090491906132af565b506001600160a01b0382161561294757806128c483856122b6565b10156128fe57604080518082018252600681526531313131303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b61290c81610f7184866122b6565b6001600160a01b038084166000908152600e602090815260408083209388168352929052205560035461293f9082613160565b6003556129d2565b6001600160a01b03831660009081526008602052604090205481111561299b57604080518082018252600681526518989898981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b6129a881610f7185611cb2565b6001600160a01b0384166000908152600860205260409020556003546129ce9082613160565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef084604051612a2191815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612a785760405162461bcd60e51b815260040161090491906132af565b508584141580612a885750858214155b15612ac157604080518082018252600681526531313135303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60005b86811015612b4457612b3b888883818110612ae157612ae1613783565b9050602002016020810190612af6919061343d565b878784818110612b0857612b08613783565b9050602002016020810190612b1d919061343d565b868685818110612b2f57612b2f613783565b90506020020135610ecc565b50600101612ac4565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612b9b5760405162461bcd60e51b815260040161090491906132af565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612be05760405162461bcd60e51b815260040161090491906132af565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c855760405162461bcd60e51b815260040161090491906132af565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612cf15760405162461bcd60e51b815260040161090491906132af565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c8181548110612d2057600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612da25760405162461bcd60e51b815260040161090491906132af565b506001600160a01b03821615612dfb57612dc081610f9d84866122b6565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612df39082613173565b600355612e32565b612e0881610f9d85611cb2565b6001600160a01b038416600090815260086020526040902055600354612e2e9082613173565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc84604051612a2191815260200190565b60045460408051808201909152600681526531313033303160d01b60208201526000916001600160a01b03868116911614612ecf5760405162461bcd60e51b815260040161090491906132af565b50612edd83610f7133611cb2565b33600090815260086020526040902055612efa83610f9d86611cb2565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90612f3f9033908890889060040161383b565b600060405180830381600087803b158015612f5957600080fd5b505af1158015612f6d573d6000803e3d6000fd5b50506040518681526001600160a01b0388169250339150600080516020613a85833981519152906020016110a0565b6004546000906001600160a01b03163314801590612fc15750600b5460ff1615156001145b15612ffa57604080518082018252600681526531313032303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6000546001600160a01b038581169116148015906130265750600954600160a01b900460ff1615156001145b156130e55760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa15801561307f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130a39190613973565b60408051808201909152600681526518989819181960d11b60208201529015156001146130e35760405162461bcd60e51b815260040161090491906132af565b505b6130f283610f7133611cb2565b3360009081526008602052604090205561310f83610f9d86611cb2565b6001600160a01b038516600081815260086020526040908190209290925590513390600080516020613a858339815191529061314e9087815260200190565b60405180910390a35060019392505050565b600061316c82846137c2565b9392505050565b600061316c82846137af565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126131a657600080fd5b813567ffffffffffffffff808211156131c1576131c161317f565b604051601f8301601f19908116603f011681019082821181831017156131e9576131e961317f565b8160405283815286602085880101111561320257600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561323557600080fd5b82359150602083013567ffffffffffffffff81111561325357600080fd5b61325f85828601613195565b9150509250929050565b6000815180845260005b8181101561328f57602081850181015186830182015201613273565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061316c6020830184613269565b60008083601f8401126132d457600080fd5b50813567ffffffffffffffff8111156132ec57600080fd5b6020830191508360208260051b850101111561330757600080fd5b9250929050565b6000806000806040858703121561332457600080fd5b843567ffffffffffffffff8082111561333c57600080fd5b613348888389016132c2565b9096509450602087013591508082111561336157600080fd5b5061336e878288016132c2565b95989497509550505050565b801515811461338857600080fd5b50565b60006020828403121561339d57600080fd5b813561316c8161337a565b80356001600160a01b03811681146133bf57600080fd5b919050565b6000806000606084860312156133d957600080fd5b6133e2846133a8565b92506133f0602085016133a8565b9150604084013590509250925092565b60006020828403121561341257600080fd5b813567ffffffffffffffff81111561342957600080fd5b61343584828501613195565b949350505050565b60006020828403121561344f57600080fd5b61316c826133a8565b60006020828403121561346a57600080fd5b813560ff8116811461316c57600080fd5b6000806040838503121561348e57600080fd5b613497836133a8565b946020939093013593505050565b600080600080608085870312156134bb57600080fd5b6134c4856133a8565b93506134d2602086016133a8565b925060408501359150606085013567ffffffffffffffff8111156134f557600080fd5b61350187828801613195565b91505092959194509250565b60008060006060848603121561352257600080fd5b61352b846133a8565b925060208401359150604084013567ffffffffffffffff81111561354e57600080fd5b61355a86828701613195565b9150509250925092565b83815260606020820152600061357d6060830185613269565b828103604084015261358f8185613269565b9695505050505050565b600080600080600080606087890312156135b257600080fd5b863567ffffffffffffffff808211156135ca57600080fd5b6135d68a838b016132c2565b909850965060208901359150808211156135ef57600080fd5b6135fb8a838b016132c2565b9096509450604089013591508082111561361457600080fd5b5061362189828a016132c2565b979a9699509497509295939492505050565b8381528260208201526060604082015260006136526060830184613269565b95945050505050565b60008060006060848603121561367057600080fd5b83359250602084013567ffffffffffffffff8082111561368f57600080fd5b61369b87838801613195565b935060408601359150808211156136b157600080fd5b5061355a86828701613195565b600080604083850312156136d157600080fd5b6136da836133a8565b91506136e8602084016133a8565b90509250929050565b600080600080600060a0868803121561370957600080fd5b613712866133a8565b9450613720602087016133a8565b935061372e604087016133a8565b925060608601359150608086013567ffffffffffffffff81111561375157600080fd5b61375d88828901613195565b9150509295509295909350565b60006020828403121561377c57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561225a5761225a613799565b8181038181111561225a5761225a613799565b6001600160a01b0384811682528316602082015260606040820181905260009061365290830184613269565b600181811c9082168061381557607f821691505b60208210810361383557634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b03841681528260208201526060604082015260006136526060830184613269565b601f8211156138ae576000816000526020600020601f850160051c8101602086101561388b5750805b601f850160051c820191505b818110156138aa57828155600101613897565b5050505b505050565b815167ffffffffffffffff8111156138cd576138cd61317f565b6138e1816138db8454613801565b84613862565b602080601f83116001811461391657600084156138fe5750858301515b600019600386901b1c1916600185901b1785556138aa565b600085815260208120601f198616915b8281101561394557888601518255948401946001909101908401613926565b50858210156139635787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121561398557600080fd5b815161316c8161337a565b8281526040602082015260006134356040830184613269565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061358f90830184613269565b600081546139e981613801565b808552602060018381168015613a065760018114613a2057613a4e565b60ff1985168884015283151560051b880183019550613a4e565b866000528260002060005b85811015613a465781548a8201860152908301908401613a2b565b890184019650505b505050505092915050565b838152606060208201526000613a7260608301856139dc565b828103604084015261358f81856139dc56feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212202f59b4608df7aba4398b68008895ae4247fc729d20f7173cdd3960dbb00144a864736f6c63430008170033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106103af5760003560e01c806383850bcb116101f4578063c267ce5f1161011a578063e8050052116100ad578063fd0e85691161007c578063fd0e856914610801578063fd14a22214610815578063fef1b9c014610828578063ff12dcea1461086857600080fd5b8063e8050052146107bb578063edaab208146107ce578063f2fde38b146107db578063f7502a7c146107ee57600080fd5b8063d87f0986116100e9578063d87f098614610757578063d93220581461076a578063db20266f1461077d578063e7464db1146107a857600080fd5b8063c267ce5f1461070b578063c7109ec91461071e578063ca5cbbb514610731578063cb7610151461074457600080fd5b8063996f3a4b11610192578063b2a90a6011610161578063b2a90a60146106be578063b967a52e146106d2578063b9b3e06a146106e5578063bfe30fb0146106f857600080fd5b8063996f3a4b146106725780639cd23707146106855780639d8886c814610698578063a9059cbb146106ab57600080fd5b80638ed8a9bc116101ce5780638ed8a9bc1461062157806392ff0d31146106345780639301a78b1461064857806395d89b411461066a57600080fd5b806383850bcb146105fe5780638da5cb5b146106065780638db9877c1461061957600080fd5b806340615cf8116102d95780635c40f6f41161027757806376fa7a311161024657806376fa7a31146105af5780637da68d34146105c25780637ed6d103146105cb57806381ebdd79146105de57600080fd5b80635c40f6f4146105695780635ccef3e71461057c5780636f3b993f1461058f57806370a082311461059c57600080fd5b8063428630f6116102b3578063428630f61461053957806358c3b870146105415780635b473327146105495780635c2c20051461056057600080fd5b806340615cf81461050057806340d1c4411461051357806340eba90e1461052657600080fd5b8063200d2ed21161035157806327e235e31161032057806327e235e3146104b25780632e027abe146104d257806331cef447146104e557806336f7ab5e146104f857600080fd5b8063200d2ed21461046c57806323b872dd1461047957806325287d421461048c57806325d608611461049f57600080fd5b80630ed5a9331161038d5780630ed5a93314610412578063153a1f3e1461042f57806318160ddd146104425780631935a8801461045957600080fd5b806306eaa0b7146103b457806306fdde03146103c95780630af7eb0f146103e7575b600080fd5b6103c76103c2366004613222565b61087b565b005b6103d1610b45565b6040516103de91906132af565b60405180910390f35b6009546103fa906001600160a01b031681565b6040516001600160a01b0390911681526020016103de565b60135461041f9060ff1681565b60405190151581526020016103de565b61041f61043d36600461330e565b610bd3565b61044b60035481565b6040519081526020016103de565b6103c761046736600461338b565b610e3b565b60075461041f9060ff1681565b61041f6104873660046133c4565b610ecc565b6004546103fa906001600160a01b031681565b6103c76104ad366004613400565b6110b3565b61044b6104c036600461343d565b60086020526000908152604090205481565b6103d16104e0366004613458565b61110d565b6103c76104f3366004613222565b611126565b6103d16112a1565b6103c761050e36600461347b565b6112ae565b6103c76105213660046134a5565b61134c565b6103c761053436600461350d565b61147b565b6103d1611762565b6103d161176f565b61055161177c565b6040516103de93929190613564565b61044b600f5481565b6103c761057736600461338b565b6118a1565b6103c761058a366004613222565b611930565b600b5461041f9060ff1681565b61044b6105aa36600461343d565b611cb2565b6103c76105bd36600461343d565b611ccd565b61044b60125481565b6103c76105d9366004613599565b611d39565b61044b6105ec36600461343d565b600d6020526000908152604090205481565b6103c7611e56565b6000546103fa906001600160a01b031681565b6103d1611ed8565b6103c761062f366004613400565b611ee5565b60095461041f90600160a81b900460ff1681565b61065b61065636600461343d565b611f3b565b6040516103de93929190613633565b6103d1611f63565b6103c761068036600461365b565b611f70565b6103c761069336600461338b565b61201d565b6103c76106a6366004613599565b612085565b61041f6106b936600461347b565b612199565b60095461041f90600160b01b900460ff1681565b6103c76106e0366004613400565b612260565b61044b6106f33660046136be565b6122b6565b6103c76107063660046134a5565b6122e1565b6103c761071936600461350d565b6123bc565b6103c761072c36600461338b565b612499565b6103c761073f3660046136f1565b61252c565b6103c761075236600461376a565b61267b565b6103c76107653660046136f1565b6126ca565b6103c7610778366004613400565b61280a565b61044b61078b3660046136be565b600e60209081526000928352604080842090915290825290205481565b6103c76107b63660046133c4565b612860565b61041f6107c9366004613599565b612a2e565b60175461041f9060ff1681565b6103c76107e936600461343d565b612b52565b6103c76107fc36600461343d565b612c3c565b60095461041f90600160a01b900460ff1681565b6103c761082336600461338b565b612ca8565b61083b61083636600461376a565b612d10565b604080516001600160a01b03958616815294909316602085015291830152151560608201526080016103de565b6103c76108763660046133c4565b612d59565b336001600160a01b0316600c838154811061089857610898613783565b60009182526020909120600490910201546001600160a01b0316148015906108cb57506000546001600160a01b03163314155b1561090d57604080518082018252600681526531313038303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60405180910390fd5b600c828154811061092057610920613783565b6000918252602082206003600490920201015460ff16151590036109725760408051808201825260068152651898981c181960d11b6020820152905162461bcd60e51b815261090491906004016132af565b600c828154811061098557610985613783565b90600052602060002090600402016002015460086000600c85815481106109ae576109ae613783565b600091825260208083206004909202909101546001600160a01b03168352820192909252604001812080549091906109e79084906137af565b9091555050600c805483908110610a0057610a00613783565b906000526020600020906004020160020154600d6000600c8581548110610a2957610a29613783565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610a629084906137c2565b925050819055506000600c8381548110610a7e57610a7e613783565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610adb57610adb613783565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610b0c57610b0c613783565b6000918252602090912060016004909202010154604051610b3992916001600160a01b03169086906137d5565b60405180910390a25050565b60018054610b5290613801565b80601f0160208091040260200160405190810160405280929190818152602001828054610b7e90613801565b8015610bcb5780601f10610ba057610100808354040283529160200191610bcb565b820191906000526020600020905b815481529060010190602001808311610bae57829003601f168201915b505050505081565b600b5460009060ff161515600103610c1957604080518082018252600681526531313035303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b838214610c545760408051808201825260068152651898981a981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b6000805b85811015610c8e57848482818110610c7257610c72613783565b9050602002013582610c8491906137af565b9150600101610c58565b5080610c9933611cb2565b1015610cd357604080518082018252600681526531313035303360d01b6020820152905162461bcd60e51b815261090491906004016132af565b6004546001600160a01b03163314610d34576009546040805180820190915260068152650c4c4c0d4c0d60d21b602082015290600160a81b900460ff161515600114610d325760405162461bcd60e51b815260040161090491906132af565b505b606060006001935060005b87811015610e2f57610d77898983818110610d5c57610d5c613783565b9050602002016020810190610d71919061343d565b3b151590565b15610dcc57610dc5898983818110610d9157610d91613783565b9050602002016020810190610da6919061343d565b888884818110610db857610db8613783565b9050602002013585612e81565b9150610e18565b610e15898983818110610de157610de1613783565b9050602002016020810190610df6919061343d565b888884818110610e0857610e08613783565b9050602002013585612f9c565b91505b811515600003610e2757600094505b600101610d3f565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610e845760405162461bcd60e51b815260040161090491906132af565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f9060200160405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610f165760405162461bcd60e51b815260040161090491906132af565b5081610f2185611cb2565b1015610f5b57604080518082018252600681526531313036303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6060833b1561102057610f7783610f7187611cb2565b90613160565b6001600160a01b038616600090815260086020526040902055610fa383610f9d86611cb2565b90613173565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90610fe89033908890879060040161383b565b600060405180830381600087803b15801561100257600080fd5b505af1158015611016573d6000803e3d6000fd5b505050505061106d565b61102d83610f7187611cb2565b6001600160a01b03861660009081526008602052604090205561105383610f9d86611cb2565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b0316600080516020613a85833981519152856040516110a091815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146110fc5760405162461bcd60e51b815260040161090491906132af565b50601161110982826138b3565b5050565b60186020526000908152604090208054610b5290613801565b60095460408051808201909152600681526531313130303160d01b602082015290600160b01b900460ff1615156001146111735760405162461bcd60e51b815260040161090491906132af565b50600954600160a01b900460ff1615156001036112425760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa1580156111dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112009190613973565b60408051808201909152600681526518989898181960d11b60208201529015156001146112405760405162461bcd60e51b815260040161090491906132af565b505b336000908152600a6020526040902082815560020161126182826138b3565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c03383836040516112959392919061383b565b60405180910390a15050565b60058054610b5290613801565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146112f75760405162461bcd60e51b815260040161090491906132af565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b19101611295565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113955760405162461bcd60e51b815260040161090491906132af565b50816113a084611cb2565b10156113da57604080518082018252600681526531313136303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6113e782610f7185611cb2565b6001600160a01b03841660009081526008602052604090205561140e82610f9d86866122b6565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af516029061146d9086908690613990565b60405180910390a350505050565b600b5460ff1615806114975750600954600160a81b900460ff16155b806114a95750816114a733611cb2565b105b156114e257604080518082018252600681526531313037303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6000546001600160a01b0384811691161480159061150e5750600954600160a01b900460ff1615156001145b156115cd5760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015611567573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061158b9190613973565b6040805180820190915260068152651898981b981960d11b60208201529015156001146115cb5760405162461bcd60e51b815260040161090491906132af565b505b33600090815260086020526040812080548492906115ec9084906137c2565b9091555050336000908152600d6020526040812080548492906116109084906137af565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a91611754918890889088906139a9565b60405180910390a250505050565b60068054610b5290613801565b60118054610b5290613801565b601480546015805491929161179090613801565b80601f01602080910402602001604051908101604052809291908181526020018280546117bc90613801565b80156118095780601f106117de57610100808354040283529160200191611809565b820191906000526020600020905b8154815290600101906020018083116117ec57829003601f168201915b50505050509080600201805461181e90613801565b80601f016020809104026020016040519081016040528092919081815260200182805461184a90613801565b80156118975780601f1061186c57610100808354040283529160200191611897565b820191906000526020600020905b81548152906001019060200180831161187a57829003601f168201915b5050505050905083565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146118ea5760405162461bcd60e51b815260040161090491906132af565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119795760405162461bcd60e51b815260040161090491906132af565b50600954600160a81b900460ff1615156000036119c457604080518082018252600681526531313039303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b600c82815481106119d7576119d7613783565b6000918252602082206003600490920201015460ff1615159003611a295760408051808201825260068152651898981c981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b600c8281548110611a3c57611a3c613783565b90600052602060002090600402016002015460086000600c8581548110611a6557611a65613783565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611aa29084906137af565b9091555050600c805483908110611abb57611abb613783565b906000526020600020906004020160020154600d6000600c8581548110611ae457611ae4613783565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611b1d9084906137c2565b925050819055506000600c8381548110611b3957611b39613783565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611b9657611b96613783565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611bc757611bc7613783565b6000918252602090912060016004909202010154604051611bf492916001600160a01b03169086906137d5565b60405180910390a2600c8281548110611c0f57611c0f613783565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c4357611c43613783565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613a85833981519152919086908110611c8557611c85613783565b906000526020600020906004020160020154604051611ca691815260200190565b60405180910390a35050565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d165760405162461bcd60e51b815260040161090491906132af565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d825760405162461bcd60e51b815260040161090491906132af565b508483141580611d925750848114155b15611dcb57604080518082018252600681526531313134303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60005b85811015611e4d57611e45878783818110611deb57611deb613783565b9050602002016020810190611e00919061343d565b868684818110611e1257611e12613783565b9050602002016020810190611e27919061343d565b858585818110611e3957611e39613783565b90506020020135612860565b600101611dce565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e9f5760405162461bcd60e51b815260040161090491906132af565b506013805460ff191660011790556040517f8298c3671093b19970d7c94ce1f23925a962f36fec31d25075d9be072b73e10390600090a1565b60108054610b5290613801565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f2e5760405162461bcd60e51b815260040161090491906132af565b50600661110982826138b3565b600a602052600090815260409020805460018201546002830180549293919261181e90613801565b60028054610b5290613801565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611fb95760405162461bcd60e51b815260040161090491906132af565b5060148390556015611fcb83826138b3565b506016611fd882826138b3565b506014546040517ff89b6034f2b759ebb2dfb08461be917a1b54f2e0523c895e7f9b5e5e14dd013e9161201091601590601690613a59565b60405180910390a1505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120665760405162461bcd60e51b815260040161090491906132af565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120ce5760405162461bcd60e51b815260040161090491906132af565b5084831415806120de5750848114155b1561211757604080518082018252600681526531313133303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60005b85811015611e4d5761219187878381811061213757612137613783565b905060200201602081019061214c919061343d565b86868481811061215e5761215e613783565b9050602002016020810190612173919061343d565b85858581811061218557612185613783565b90506020020135612d59565b60010161211a565b6000816121a533611cb2565b10156040518060400160405280600681526020016531313034303160d01b815250906121e45760405162461bcd60e51b815260040161090491906132af565b506009546040805180820190915260068152651898981a181960d11b602082015290600160a81b900460ff1615156001146122325760405162461bcd60e51b815260040161090491906132af565b506060833b1561224f57612247848483612e81565b91505061225a565b612247848483612f9c565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146122a95760405162461bcd60e51b815260040161090491906132af565b50600561110982826138b3565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b816122ec33866122b6565b101561232657604080518082018252600681526518989818981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b61233482610f7133876122b6565b336000908152600e602090815260408083206001600160a01b038916845290915290205561236582610f9d85611cb2565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc9061146d9087908790879061383b565b816123c633611cb2565b101561240057604080518082018252600681526518989818181960d11b6020820152905162461bcd60e51b815261090491906004016132af565b61240d82610f7133611cb2565b33600081815260086020526040902091909155612431908390610f9d9086906122b6565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f5139061248c9086908690613990565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146124e25760405162461bcd60e51b815260040161090491906132af565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146125755760405162461bcd60e51b815260040161090491906132af565b508161258186866122b6565b10156125bb57604080518082018252600681526531313132303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6125c982610f7187876122b6565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556125fc82610f9d85611cb2565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161266c9392919061383b565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126c45760405162461bcd60e51b815260040161090491906132af565b50601255565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146127135760405162461bcd60e51b815260040161090491906132af565b508161271f86866122b6565b101561275957604080518082018252600681526531313137303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b61276782610f7187876122b6565b6001600160a01b038087166000908152600e602090815260408083209389168352929052205561279b82610f9d87866122b6565b6001600160a01b038087166000818152600e602090815260408083208986168085529252918290209490945551918716917f5847ffcc1bce0ecfdd115863db8562df2b18b61698938e251adf381774e9e3be906127fb9087908790613990565b60405180910390a45050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128535760405162461bcd60e51b815260040161090491906132af565b50601061110982826138b3565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128a95760405162461bcd60e51b815260040161090491906132af565b506001600160a01b0382161561294757806128c483856122b6565b10156128fe57604080518082018252600681526531313131303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b61290c81610f7184866122b6565b6001600160a01b038084166000908152600e602090815260408083209388168352929052205560035461293f9082613160565b6003556129d2565b6001600160a01b03831660009081526008602052604090205481111561299b57604080518082018252600681526518989898981960d11b6020820152905162461bcd60e51b815261090491906004016132af565b6129a881610f7185611cb2565b6001600160a01b0384166000908152600860205260409020556003546129ce9082613160565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef084604051612a2191815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612a785760405162461bcd60e51b815260040161090491906132af565b508584141580612a885750858214155b15612ac157604080518082018252600681526531313135303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b60005b86811015612b4457612b3b888883818110612ae157612ae1613783565b9050602002016020810190612af6919061343d565b878784818110612b0857612b08613783565b9050602002016020810190612b1d919061343d565b868685818110612b2f57612b2f613783565b90506020020135610ecc565b50600101612ac4565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612b9b5760405162461bcd60e51b815260040161090491906132af565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612be05760405162461bcd60e51b815260040161090491906132af565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c855760405162461bcd60e51b815260040161090491906132af565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612cf15760405162461bcd60e51b815260040161090491906132af565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c8181548110612d2057600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612da25760405162461bcd60e51b815260040161090491906132af565b506001600160a01b03821615612dfb57612dc081610f9d84866122b6565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612df39082613173565b600355612e32565b612e0881610f9d85611cb2565b6001600160a01b038416600090815260086020526040902055600354612e2e9082613173565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc84604051612a2191815260200190565b60045460408051808201909152600681526531313033303160d01b60208201526000916001600160a01b03868116911614612ecf5760405162461bcd60e51b815260040161090491906132af565b50612edd83610f7133611cb2565b33600090815260086020526040902055612efa83610f9d86611cb2565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a90612f3f9033908890889060040161383b565b600060405180830381600087803b158015612f5957600080fd5b505af1158015612f6d573d6000803e3d6000fd5b50506040518681526001600160a01b0388169250339150600080516020613a85833981519152906020016110a0565b6004546000906001600160a01b03163314801590612fc15750600b5460ff1615156001145b15612ffa57604080518082018252600681526531313032303160d01b6020820152905162461bcd60e51b815261090491906004016132af565b6000546001600160a01b038581169116148015906130265750600954600160a01b900460ff1615156001145b156130e55760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa15801561307f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130a39190613973565b60408051808201909152600681526518989819181960d11b60208201529015156001146130e35760405162461bcd60e51b815260040161090491906132af565b505b6130f283610f7133611cb2565b3360009081526008602052604090205561310f83610f9d86611cb2565b6001600160a01b038516600081815260086020526040908190209290925590513390600080516020613a858339815191529061314e9087815260200190565b60405180910390a35060019392505050565b600061316c82846137c2565b9392505050565b600061316c82846137af565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126131a657600080fd5b813567ffffffffffffffff808211156131c1576131c161317f565b604051601f8301601f19908116603f011681019082821181831017156131e9576131e961317f565b8160405283815286602085880101111561320257600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561323557600080fd5b82359150602083013567ffffffffffffffff81111561325357600080fd5b61325f85828601613195565b9150509250929050565b6000815180845260005b8181101561328f57602081850181015186830182015201613273565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061316c6020830184613269565b60008083601f8401126132d457600080fd5b50813567ffffffffffffffff8111156132ec57600080fd5b6020830191508360208260051b850101111561330757600080fd5b9250929050565b6000806000806040858703121561332457600080fd5b843567ffffffffffffffff8082111561333c57600080fd5b613348888389016132c2565b9096509450602087013591508082111561336157600080fd5b5061336e878288016132c2565b95989497509550505050565b801515811461338857600080fd5b50565b60006020828403121561339d57600080fd5b813561316c8161337a565b80356001600160a01b03811681146133bf57600080fd5b919050565b6000806000606084860312156133d957600080fd5b6133e2846133a8565b92506133f0602085016133a8565b9150604084013590509250925092565b60006020828403121561341257600080fd5b813567ffffffffffffffff81111561342957600080fd5b61343584828501613195565b949350505050565b60006020828403121561344f57600080fd5b61316c826133a8565b60006020828403121561346a57600080fd5b813560ff8116811461316c57600080fd5b6000806040838503121561348e57600080fd5b613497836133a8565b946020939093013593505050565b600080600080608085870312156134bb57600080fd5b6134c4856133a8565b93506134d2602086016133a8565b925060408501359150606085013567ffffffffffffffff8111156134f557600080fd5b61350187828801613195565b91505092959194509250565b60008060006060848603121561352257600080fd5b61352b846133a8565b925060208401359150604084013567ffffffffffffffff81111561354e57600080fd5b61355a86828701613195565b9150509250925092565b83815260606020820152600061357d6060830185613269565b828103604084015261358f8185613269565b9695505050505050565b600080600080600080606087890312156135b257600080fd5b863567ffffffffffffffff808211156135ca57600080fd5b6135d68a838b016132c2565b909850965060208901359150808211156135ef57600080fd5b6135fb8a838b016132c2565b9096509450604089013591508082111561361457600080fd5b5061362189828a016132c2565b979a9699509497509295939492505050565b8381528260208201526060604082015260006136526060830184613269565b95945050505050565b60008060006060848603121561367057600080fd5b83359250602084013567ffffffffffffffff8082111561368f57600080fd5b61369b87838801613195565b935060408601359150808211156136b157600080fd5b5061355a86828701613195565b600080604083850312156136d157600080fd5b6136da836133a8565b91506136e8602084016133a8565b90509250929050565b600080600080600060a0868803121561370957600080fd5b613712866133a8565b9450613720602087016133a8565b935061372e604087016133a8565b925060608601359150608086013567ffffffffffffffff81111561375157600080fd5b61375d88828901613195565b9150509295509295909350565b60006020828403121561377c57600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561225a5761225a613799565b8181038181111561225a5761225a613799565b6001600160a01b0384811682528316602082015260606040820181905260009061365290830184613269565b600181811c9082168061381557607f821691505b60208210810361383557634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b03841681528260208201526060604082015260006136526060830184613269565b601f8211156138ae576000816000526020600020601f850160051c8101602086101561388b5750805b601f850160051c820191505b818110156138aa57828155600101613897565b5050505b505050565b815167ffffffffffffffff8111156138cd576138cd61317f565b6138e1816138db8454613801565b84613862565b602080601f83116001811461391657600084156138fe5750858301515b600019600386901b1c1916600185901b1785556138aa565b600085815260208120601f198616915b8281101561394557888601518255948401946001909101908401613926565b50858210156139635787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121561398557600080fd5b815161316c8161337a565b8281526040602082015260006134356040830184613269565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061358f90830184613269565b600081546139e981613801565b808552602060018381168015613a065760018114613a2057613a4e565b60ff1985168884015283151560051b880183019550613a4e565b866000528260002060005b85811015613a465781548a8201860152908301908401613a2b565b890184019650505b505050505092915050565b838152606060208201526000613a7260608301856139dc565b828103604084015261358f81856139dc56feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212202f59b4608df7aba4398b68008895ae4247fc729d20f7173cdd3960dbb00144a864736f6c63430008170033" } \ No newline at end of file diff --git a/app/contracts/json/IbetStraightBond.json b/app/contracts/json/IbetStraightBond.json index 57e9cf20f..f937a0ce3 100644 --- a/app/contracts/json/IbetStraightBond.json +++ b/app/contracts/json/IbetStraightBond.json @@ -275,6 +275,43 @@ "name": "ChangeTransferApprovalRequired", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "beforeAccountAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "afterAccountAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "name": "ForceChangeLockedAccount", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -884,6 +921,39 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_lockAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_beforeAccountAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_afterAccountAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_data", + "type": "string" + } + ], + "name": "forceChangeLockedAccount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1722,6 +1792,6 @@ "type": "function" } ], - "bytecode": "60806040523480156200001157600080fd5b5060405162004011380380620040118339810160408190526200003491620001f5565b600080546001600160a01b0319908116339182161717905560016200005a8c8262000431565b506002620000698b8262000431565b506003899055600f889055601962000082888262000431565b50601262000091878262000431565b506013859055601b620000a5858262000431565b506014620000b4848262000431565b506015620000c3838262000431565b506016620000d2828262000431565b5050600354600080546001600160a01b031681526008602052604090205550506018805460ff1990811690915560078054909116600117905550506009805460ff60a01b1916600160a01b17905550620004fd945050505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200015557600080fd5b81516001600160401b03808211156200017257620001726200012d565b604051601f8301601f19908116603f011681019082821181831017156200019d576200019d6200012d565b8160405283815260209250866020858801011115620001bb57600080fd5b600091505b83821015620001df5785820183015181830184015290820190620001c0565b6000602085830101528094505050505092915050565b60008060008060008060008060008060006101608c8e0312156200021857600080fd5b8b516001600160401b038111156200022f57600080fd5b6200023d8e828f0162000143565b60208e0151909c5090506001600160401b038111156200025c57600080fd5b6200026a8e828f0162000143565b9a505060408c0151985060608c0151975060808c015160018060401b038111156200029457600080fd5b620002a28e828f0162000143565b60a08e015190985090506001600160401b03811115620002c157600080fd5b620002cf8e828f0162000143565b60c08e015160e08f0151919850965090506001600160401b03811115620002f557600080fd5b620003038e828f0162000143565b6101008e015190955090506001600160401b038111156200032357600080fd5b620003318e828f0162000143565b6101208e015190945090506001600160401b038111156200035157600080fd5b6200035f8e828f0162000143565b6101408e015190935090506001600160401b038111156200037f57600080fd5b6200038d8e828f0162000143565b9150509295989b509295989b9093969950565b600181811c90821680620003b557607f821691505b602082108103620003d657634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200042c576000816000526020600020601f850160051c81016020861015620004075750805b601f850160051c820191505b81811015620004285782815560010162000413565b5050505b505050565b81516001600160401b038111156200044d576200044d6200012d565b62000465816200045e8454620003a0565b84620003dc565b602080601f8311600181146200049d5760008415620004845750858301515b600019600386901b1c1916600185901b17855562000428565b600085815260208120601f198616915b82811015620004ce57888601518255948401946001909101908401620004ad565b5085821015620004ed5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b613b04806200050d6000396000f3fe608060405234801561001057600080fd5b50600436106104335760003560e01c806381ebdd7911610236578063c7109ec91161013b578063ef4474cd116100c3578063fc590d0911610087578063fc590d09146108fd578063fd0e856914610905578063fd14a22214610919578063fef1b9c01461092c578063ff12dcea1461096c57600080fd5b8063ef4474cd146108b3578063ef8032ef146108bc578063f2fde38b146108cf578063f35eeaef146108e2578063f7502a7c146108ea57600080fd5b8063e7464db11161010a578063e7464db11461085f578063e805005214610872578063eb68757f14610885578063ed62c2a014610898578063ee0cfa40146108a057600080fd5b8063c7109ec914610806578063ca5cbbb514610819578063d5d9d0c71461082c578063db20266f1461083457600080fd5b80639cd23707116101be578063b2a90a601161018d578063b2a90a60146107a6578063b967a52e146107ba578063b9b3e06a146107cd578063bfe30fb0146107e0578063c267ce5f146107f357600080fd5b80639cd237071461075a5780639d8886c81461076d578063a9059cbb14610780578063ab62f92e1461079357600080fd5b8063919fe52d11610205578063919fe52d146106f657806392ff0d31146107095780639301a78b1461071d57806395d89b411461073f57806397e4c5131461074757600080fd5b806381ebdd791461069d5780638ac08bf1146106bd5780638da5cb5b146106d05780638ed8a9bc146106e357600080fd5b806340615cf81161033c5780635f84f302116102c457806370740aab1161029357806370740aab1461065357806370a082311461065b57806376fa7a311461066e5780637c3a00fd146106815780637ed6d1031461068a57600080fd5b80635f84f30214610618578063637834441461062b5780636666e49c1461063e5780636f3b993f1461064657600080fd5b806344fd9caa1161030b57806344fd9caa146105d957806358c3b870146105e257806359d93617146105ea5780635c40f6f4146105f25780635ccef3e71461060557600080fd5b806340615cf81461059857806340d1c441146105ab57806340eba90e146105be578063428630f6146105d157600080fd5b806325287d42116103bf57806331cef4471161038e57806331cef4471461055a578063329dbddc1461056d57806336f7ab5e146105755780633a2b69391461057d5780633b18b9841461058557600080fd5b806325287d421461050c57806325d608611461051f57806327e235e3146105325780632e0ad0041461055257600080fd5b8063153a1f3e11610406578063153a1f3e146104af57806318160ddd146104c25780631935a880146104d9578063200d2ed2146104ec57806323b872dd146104f957600080fd5b8063034f6b211461043857806306eaa0b71461045a57806306fdde031461046f5780630af7eb0f14610484575b600080fd5b6018546104459060ff1681565b60405190151581526020015b60405180910390f35b61046d6104683660046133a5565b61097f565b005b610477610c49565b6040516104519190613432565b600954610497906001600160a01b031681565b6040516001600160a01b039091168152602001610451565b6104456104bd366004613491565b610cd7565b6104cb60035481565b604051908152602001610451565b61046d6104e736600461350e565b610efb565b6007546104459060ff1681565b610445610507366004613547565b610f8d565b600454610497906001600160a01b031681565b61046d61052d366004613583565b611174565b6104cb6105403660046135c0565b60086020526000908152604090205481565b6104776111ce565b61046d6105683660046133a5565b6111db565b610477611356565b610477611363565b610477611370565b61046d6105933660046135db565b61137d565b61046d6105a63660046135f4565b6113fc565b61046d6105b936600461361e565b61149a565b61046d6105cc366004613686565b6115c9565b6104776118b0565b6104cb600f5481565b6104776118bd565b6104776118ca565b61046d61060036600461350e565b6118d7565b61046d6106133660046133a5565b611966565b61046d6106263660046135db565b611ce8565b61046d610639366004613583565b611d37565b610477611d8d565b600b546104459060ff1681565b610477611d9a565b6104cb6106693660046135c0565b611da7565b61046d61067c3660046135c0565b611dc2565b6104cb60105481565b61046d6106983660046136dd565b611e2e565b6104cb6106ab3660046135c0565b600d6020526000908152604090205481565b61046d6106cb366004613583565b611f4b565b600054610497906001600160a01b031681565b61046d6106f1366004613583565b611fa1565b61046d610704366004613583565b611ff7565b60095461044590600160a81b900460ff1681565b61073061072b3660046135c0565b61204d565b60405161045193929190613777565b6104776120f8565b61046d610755366004613583565b612105565b61046d61076836600461350e565b61215b565b61046d61077b3660046136dd565b6121c3565b61044561078e3660046135f4565b6122d7565b61046d6107a1366004613583565b61239e565b60095461044590600160b01b900460ff1681565b61046d6107c8366004613583565b6123f4565b6104cb6107db36600461379f565b61244a565b61046d6107ee36600461361e565b612475565b61046d610801366004613686565b612550565b61046d61081436600461350e565b61262d565b61046d6108273660046137d2565b6126c0565b61046d61280f565b6104cb61084236600461379f565b600e60209081526000928352604080842090915290825290205481565b61046d61086d366004613547565b612891565b6104456108803660046136dd565b612a5f565b61046d610893366004613583565b612b83565b610477612bd9565b61046d6108ae366004613583565b612be6565b6104cb60135481565b61046d6108ca3660046135db565b612c3c565b61046d6108dd3660046135c0565b612cbb565b610477612da5565b61046d6108f83660046135c0565b612db2565b610477612e1e565b60095461044590600160a01b900460ff1681565b61046d61092736600461350e565b612e2b565b61093f61093a3660046135db565b612e93565b604080516001600160a01b0395861681529490931660208501529183015215156060820152608001610451565b61046d61097a366004613547565b612edc565b336001600160a01b0316600c838154811061099c5761099c61384b565b60009182526020909120600490910201546001600160a01b0316148015906109cf57506000546001600160a01b03163314155b15610a1157604080518082018252600681526531323038303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60405180910390fd5b600c8281548110610a2457610a2461384b565b6000918252602082206003600490920201015460ff1615159003610a765760408051808201825260068152651899181c181960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b600c8281548110610a8957610a8961384b565b90600052602060002090600402016002015460086000600c8581548110610ab257610ab261384b565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610aeb908490613877565b9091555050600c805483908110610b0457610b0461384b565b906000526020600020906004020160020154600d6000600c8581548110610b2d57610b2d61384b565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610b6690849061388a565b925050819055506000600c8381548110610b8257610b8261384b565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610bdf57610bdf61384b565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610c1057610c1061384b565b6000918252602090912060016004909202010154604051610c3d92916001600160a01b031690869061389d565b60405180910390a25050565b60018054610c56906138c9565b80601f0160208091040260200160405190810160405280929190818152602001828054610c82906138c9565b8015610ccf5780601f10610ca457610100808354040283529160200191610ccf565b820191906000526020600020905b815481529060010190602001808311610cb257829003601f168201915b505050505081565b6000838214610d1457604080518082018252600681526531323035303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6000805b85811015610d4e57848482818110610d3257610d3261384b565b9050602002013582610d449190613877565b9150600101610d18565b5080610d5933611da7565b1015610d935760408051808201825260068152651899181a981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6004546001600160a01b03163314610df45760095460408051808201909152600681526531323035303360d01b602082015290600160a81b900460ff161515600114610df25760405162461bcd60e51b8152600401610a089190613432565b505b606060006001935060005b87811015610eef57610e37898983818110610e1c57610e1c61384b565b9050602002016020810190610e3191906135c0565b3b151590565b15610e8c57610e85898983818110610e5157610e5161384b565b9050602002016020810190610e6691906135c0565b888884818110610e7857610e7861384b565b9050602002013585613004565b9150610ed8565b610ed5898983818110610ea157610ea161384b565b9050602002016020810190610eb691906135c0565b888884818110610ec857610ec861384b565b905060200201358561311f565b91505b811515600003610ee757600094505b600101610dff565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610f445760405162461bcd60e51b8152600401610a089190613432565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f906020015b60405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610fd75760405162461bcd60e51b8152600401610a089190613432565b5081610fe285611da7565b101561101c57604080518082018252600681526531323036303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6060833b156110e1576110388361103287611da7565b906132e3565b6001600160a01b0386166000908152600860205260409020556110648361105e86611da7565b906132f6565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a906110a990339088908790600401613903565b600060405180830381600087803b1580156110c357600080fd5b505af11580156110d7573d6000803e3d6000fd5b505050505061112e565b6110ee8361103287611da7565b6001600160a01b0386166000908152600860205260409020556111148361105e86611da7565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b0316600080516020613aaf8339815191528560405161116191815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146111bd5760405162461bcd60e51b8152600401610a089190613432565b5060176111ca828261397b565b5050565b601a8054610c56906138c9565b60095460408051808201909152600681526531323130303160d01b602082015290600160b01b900460ff1615156001146112285760405162461bcd60e51b8152600401610a089190613432565b50600954600160a01b900460ff1615156001036112f75760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa158015611291573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112b59190613a3b565b60408051808201909152600681526518991898181960d11b60208201529015156001146112f55760405162461bcd60e51b8152600401610a089190613432565b505b336000908152600a60205260409020828155600201611316828261397b565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c033838360405161134a93929190613903565b60405180910390a15050565b60118054610c56906138c9565b60058054610c56906138c9565b60198054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113c65760405162461bcd60e51b8152600401610a089190613432565b5060138190556040518181527fe451abe602c33e612ea61221b5a142f7fe3c044286e42c8340ffe7fa86a5859790602001610f82565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146114455760405162461bcd60e51b8152600401610a089190613432565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b1910161134a565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146114e35760405162461bcd60e51b8152600401610a089190613432565b50816114ee84611da7565b101561152857604080518082018252600681526531323136303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6115358261103285611da7565b6001600160a01b03841660009081526008602052604090205561155c8261105e868661244a565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af51602906115bb9086908690613a58565b60405180910390a350505050565b600b5460ff1615806115e55750600954600160a81b900460ff16155b806115f75750816115f533611da7565b105b1561163057604080518082018252600681526531323037303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6000546001600160a01b0384811691161480159061165c5750600954600160a01b900460ff1615156001145b1561171b5760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa1580156116b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116d99190613a3b565b6040805180820190915260068152651899181b981960d11b60208201529015156001146117195760405162461bcd60e51b8152600401610a089190613432565b505b336000908152600860205260408120805484929061173a90849061388a565b9091555050336000908152600d60205260408120805484929061175e908490613877565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a916118a291889088908890613a71565b60405180910390a250505050565b60068054610c56906138c9565b60178054610c56906138c9565b601b8054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119205760405162461bcd60e51b8152600401610a089190613432565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119af5760405162461bcd60e51b8152600401610a089190613432565b50600954600160a81b900460ff1615156000036119fa57604080518082018252600681526531323039303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b600c8281548110611a0d57611a0d61384b565b6000918252602082206003600490920201015460ff1615159003611a5f5760408051808201825260068152651899181c981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b600c8281548110611a7257611a7261384b565b90600052602060002090600402016002015460086000600c8581548110611a9b57611a9b61384b565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611ad8908490613877565b9091555050600c805483908110611af157611af161384b565b906000526020600020906004020160020154600d6000600c8581548110611b1a57611b1a61384b565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611b5390849061388a565b925050819055506000600c8381548110611b6f57611b6f61384b565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611bcc57611bcc61384b565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611bfd57611bfd61384b565b6000918252602090912060016004909202010154604051611c2a92916001600160a01b031690869061389d565b60405180910390a2600c8281548110611c4557611c4561384b565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c7957611c7961384b565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613aaf833981519152919086908110611cbb57611cbb61384b565b906000526020600020906004020160020154604051611cdc91815260200190565b60405180910390a35050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d315760405162461bcd60e51b8152600401610a089190613432565b50601055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d805760405162461bcd60e51b8152600401610a089190613432565b5060116111ca828261397b565b60128054610c56906138c9565b60168054610c56906138c9565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e0b5760405162461bcd60e51b8152600401610a089190613432565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e775760405162461bcd60e51b8152600401610a089190613432565b508483141580611e875750848114155b15611ec057604080518082018252600681526531323134303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60005b85811015611f4257611f3a878783818110611ee057611ee061384b565b9050602002016020810190611ef591906135c0565b868684818110611f0757611f0761384b565b9050602002016020810190611f1c91906135c0565b858585818110611f2e57611f2e61384b565b90506020020135612891565b600101611ec3565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f945760405162461bcd60e51b8152600401610a089190613432565b5060196111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611fea5760405162461bcd60e51b8152600401610a089190613432565b5060066111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120405760405162461bcd60e51b8152600401610a089190613432565b50601b6111ca828261397b565b600a6020526000908152604090208054600182015460028301805492939192612075906138c9565b80601f01602080910402602001604051908101604052809291908181526020018280546120a1906138c9565b80156120ee5780601f106120c3576101008083540402835291602001916120ee565b820191906000526020600020905b8154815290600101906020018083116120d157829003601f168201915b5050505050905083565b60028054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461214e5760405162461bcd60e51b8152600401610a089190613432565b50601c6111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146121a45760405162461bcd60e51b8152600401610a089190613432565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461220c5760405162461bcd60e51b8152600401610a089190613432565b50848314158061221c5750848114155b1561225557604080518082018252600681526531323133303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60005b85811015611f42576122cf8787838181106122755761227561384b565b905060200201602081019061228a91906135c0565b86868481811061229c5761229c61384b565b90506020020160208101906122b191906135c0565b8585858181106122c3576122c361384b565b90506020020135612edc565b600101612258565b6000816122e333611da7565b10156040518060400160405280600681526020016531323034303160d01b815250906123225760405162461bcd60e51b8152600401610a089190613432565b506009546040805180820190915260068152651899181a181960d11b602082015290600160a81b900460ff1615156001146123705760405162461bcd60e51b8152600401610a089190613432565b506060833b1561238d57612385848483613004565b915050612398565b61238584848361311f565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146123e75760405162461bcd60e51b8152600401610a089190613432565b50601a6111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461243d5760405162461bcd60e51b8152600401610a089190613432565b5060056111ca828261397b565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b81612480338661244a565b10156124ba57604080518082018252600681526518991818981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6124c882611032338761244a565b336000908152600e602090815260408083206001600160a01b03891684529091529020556124f98261105e85611da7565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc906115bb90879087908790613903565b8161255a33611da7565b101561259457604080518082018252600681526518991818181960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6125a18261103233611da7565b336000818152600860205260409020919091556125c590839061105e90869061244a565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f513906126209086908690613a58565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126765760405162461bcd60e51b8152600401610a089190613432565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146127095760405162461bcd60e51b8152600401610a089190613432565b5081612715868661244a565b101561274f57604080518082018252600681526531323132303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b61275d82611032878761244a565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556127908261105e85611da7565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161280093929190613903565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128585760405162461bcd60e51b8152600401610a089190613432565b506018805460ff191660011790556040517f96151d7f1d8c50be29a45c0e3ae9ce2008888d1615b196e8f5ffd8c178e6dde890600090a1565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128da5760405162461bcd60e51b8152600401610a089190613432565b506001600160a01b0382161561297857806128f5838561244a565b101561292f57604080518082018252600681526531323131303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b61293d81611032848661244a565b6001600160a01b038084166000908152600e602090815260408083209388168352929052205560035461297090826132e3565b600355612a03565b6001600160a01b0383166000908152600860205260409020548111156129cc57604080518082018252600681526518991898981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6129d98161103285611da7565b6001600160a01b0384166000908152600860205260409020556003546129ff90826132e3565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef084604051612a5291815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612aa95760405162461bcd60e51b8152600401610a089190613432565b508584141580612ab95750858214155b15612af257604080518082018252600681526531323135303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60005b86811015612b7557612b6c888883818110612b1257612b1261384b565b9050602002016020810190612b2791906135c0565b878784818110612b3957612b3961384b565b9050602002016020810190612b4e91906135c0565b868685818110612b6057612b6061384b565b90506020020135610f8d565b50600101612af5565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612bcc5760405162461bcd60e51b8152600401610a089190613432565b5060166111ca828261397b565b601c8054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c2f5760405162461bcd60e51b8152600401610a089190613432565b5060126111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c855760405162461bcd60e51b8152600401610a089190613432565b50600f8190556040518181527f82b19c9a00131a87eeff1903fcb9e4f2a3ba76dd86098586eea921a533e218dc90602001610f82565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612d045760405162461bcd60e51b8152600401610a089190613432565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612d495760405162461bcd60e51b8152600401610a089190613432565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60158054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612dfb5760405162461bcd60e51b8152600401610a089190613432565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60148054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612e745760405162461bcd60e51b8152600401610a089190613432565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c8181548110612ea357600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612f255760405162461bcd60e51b8152600401610a089190613432565b506001600160a01b03821615612f7e57612f438161105e848661244a565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612f7690826132f6565b600355612fb5565b612f8b8161105e85611da7565b6001600160a01b038416600090815260086020526040902055600354612fb190826132f6565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc84604051612a5291815260200190565b60045460408051808201909152600681526531323033303160d01b60208201526000916001600160a01b038681169116146130525760405162461bcd60e51b8152600401610a089190613432565b506130608361103233611da7565b3360009081526008602052604090205561307d8361105e86611da7565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a906130c290339088908890600401613903565b600060405180830381600087803b1580156130dc57600080fd5b505af11580156130f0573d6000803e3d6000fd5b50506040518681526001600160a01b0388169250339150600080516020613aaf83398151915290602001611161565b6004546000906001600160a01b031633148015906131445750600b5460ff1615156001145b1561317d57604080518082018252600681526531323032303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6000546001600160a01b038581169116148015906131a95750600954600160a01b900460ff1615156001145b156132685760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015613202573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906132269190613a3b565b60408051808201909152600681526518991819181960d11b60208201529015156001146132665760405162461bcd60e51b8152600401610a089190613432565b505b6132758361103233611da7565b336000908152600860205260409020556132928361105e86611da7565b6001600160a01b038516600081815260086020526040908190209290925590513390600080516020613aaf833981519152906132d19087815260200190565b60405180910390a35060019392505050565b60006132ef828461388a565b9392505050565b60006132ef8284613877565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261332957600080fd5b813567ffffffffffffffff8082111561334457613344613302565b604051601f8301601f19908116603f0116810190828211818310171561336c5761336c613302565b8160405283815286602085880101111561338557600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080604083850312156133b857600080fd5b82359150602083013567ffffffffffffffff8111156133d657600080fd5b6133e285828601613318565b9150509250929050565b6000815180845260005b81811015613412576020818501810151868301820152016133f6565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006132ef60208301846133ec565b60008083601f84011261345757600080fd5b50813567ffffffffffffffff81111561346f57600080fd5b6020830191508360208260051b850101111561348a57600080fd5b9250929050565b600080600080604085870312156134a757600080fd5b843567ffffffffffffffff808211156134bf57600080fd5b6134cb88838901613445565b909650945060208701359150808211156134e457600080fd5b506134f187828801613445565b95989497509550505050565b801515811461350b57600080fd5b50565b60006020828403121561352057600080fd5b81356132ef816134fd565b80356001600160a01b038116811461354257600080fd5b919050565b60008060006060848603121561355c57600080fd5b6135658461352b565b92506135736020850161352b565b9150604084013590509250925092565b60006020828403121561359557600080fd5b813567ffffffffffffffff8111156135ac57600080fd5b6135b884828501613318565b949350505050565b6000602082840312156135d257600080fd5b6132ef8261352b565b6000602082840312156135ed57600080fd5b5035919050565b6000806040838503121561360757600080fd5b6136108361352b565b946020939093013593505050565b6000806000806080858703121561363457600080fd5b61363d8561352b565b935061364b6020860161352b565b925060408501359150606085013567ffffffffffffffff81111561366e57600080fd5b61367a87828801613318565b91505092959194509250565b60008060006060848603121561369b57600080fd5b6136a48461352b565b925060208401359150604084013567ffffffffffffffff8111156136c757600080fd5b6136d386828701613318565b9150509250925092565b600080600080600080606087890312156136f657600080fd5b863567ffffffffffffffff8082111561370e57600080fd5b61371a8a838b01613445565b9098509650602089013591508082111561373357600080fd5b61373f8a838b01613445565b9096509450604089013591508082111561375857600080fd5b5061376589828a01613445565b979a9699509497509295939492505050565b83815282602082015260606040820152600061379660608301846133ec565b95945050505050565b600080604083850312156137b257600080fd5b6137bb8361352b565b91506137c96020840161352b565b90509250929050565b600080600080600060a086880312156137ea57600080fd5b6137f38661352b565b94506138016020870161352b565b935061380f6040870161352b565b925060608601359150608086013567ffffffffffffffff81111561383257600080fd5b61383e88828901613318565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561239857612398613861565b8181038181111561239857612398613861565b6001600160a01b03848116825283166020820152606060408201819052600090613796908301846133ec565b600181811c908216806138dd57607f821691505b6020821081036138fd57634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b038416815282602082015260606040820152600061379660608301846133ec565b601f821115613976576000816000526020600020601f850160051c810160208610156139535750805b601f850160051c820191505b818110156139725782815560010161395f565b5050505b505050565b815167ffffffffffffffff81111561399557613995613302565b6139a9816139a384546138c9565b8461392a565b602080601f8311600181146139de57600084156139c65750858301515b600019600386901b1c1916600185901b178555613972565b600085815260208120601f198616915b82811015613a0d578886015182559484019460019091019084016139ee565b5085821015613a2b5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215613a4d57600080fd5b81516132ef816134fd565b8281526040602082015260006135b860408301846133ec565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090613aa4908301846133ec565b969550505050505056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212205603470ddfb0f44164cb04123d9c86fdce49166a29e6c4eb3e468b682d39d3df64736f6c63430008170033", - "deployedBytecode": "608060405234801561001057600080fd5b50600436106104335760003560e01c806381ebdd7911610236578063c7109ec91161013b578063ef4474cd116100c3578063fc590d0911610087578063fc590d09146108fd578063fd0e856914610905578063fd14a22214610919578063fef1b9c01461092c578063ff12dcea1461096c57600080fd5b8063ef4474cd146108b3578063ef8032ef146108bc578063f2fde38b146108cf578063f35eeaef146108e2578063f7502a7c146108ea57600080fd5b8063e7464db11161010a578063e7464db11461085f578063e805005214610872578063eb68757f14610885578063ed62c2a014610898578063ee0cfa40146108a057600080fd5b8063c7109ec914610806578063ca5cbbb514610819578063d5d9d0c71461082c578063db20266f1461083457600080fd5b80639cd23707116101be578063b2a90a601161018d578063b2a90a60146107a6578063b967a52e146107ba578063b9b3e06a146107cd578063bfe30fb0146107e0578063c267ce5f146107f357600080fd5b80639cd237071461075a5780639d8886c81461076d578063a9059cbb14610780578063ab62f92e1461079357600080fd5b8063919fe52d11610205578063919fe52d146106f657806392ff0d31146107095780639301a78b1461071d57806395d89b411461073f57806397e4c5131461074757600080fd5b806381ebdd791461069d5780638ac08bf1146106bd5780638da5cb5b146106d05780638ed8a9bc146106e357600080fd5b806340615cf81161033c5780635f84f302116102c457806370740aab1161029357806370740aab1461065357806370a082311461065b57806376fa7a311461066e5780637c3a00fd146106815780637ed6d1031461068a57600080fd5b80635f84f30214610618578063637834441461062b5780636666e49c1461063e5780636f3b993f1461064657600080fd5b806344fd9caa1161030b57806344fd9caa146105d957806358c3b870146105e257806359d93617146105ea5780635c40f6f4146105f25780635ccef3e71461060557600080fd5b806340615cf81461059857806340d1c441146105ab57806340eba90e146105be578063428630f6146105d157600080fd5b806325287d42116103bf57806331cef4471161038e57806331cef4471461055a578063329dbddc1461056d57806336f7ab5e146105755780633a2b69391461057d5780633b18b9841461058557600080fd5b806325287d421461050c57806325d608611461051f57806327e235e3146105325780632e0ad0041461055257600080fd5b8063153a1f3e11610406578063153a1f3e146104af57806318160ddd146104c25780631935a880146104d9578063200d2ed2146104ec57806323b872dd146104f957600080fd5b8063034f6b211461043857806306eaa0b71461045a57806306fdde031461046f5780630af7eb0f14610484575b600080fd5b6018546104459060ff1681565b60405190151581526020015b60405180910390f35b61046d6104683660046133a5565b61097f565b005b610477610c49565b6040516104519190613432565b600954610497906001600160a01b031681565b6040516001600160a01b039091168152602001610451565b6104456104bd366004613491565b610cd7565b6104cb60035481565b604051908152602001610451565b61046d6104e736600461350e565b610efb565b6007546104459060ff1681565b610445610507366004613547565b610f8d565b600454610497906001600160a01b031681565b61046d61052d366004613583565b611174565b6104cb6105403660046135c0565b60086020526000908152604090205481565b6104776111ce565b61046d6105683660046133a5565b6111db565b610477611356565b610477611363565b610477611370565b61046d6105933660046135db565b61137d565b61046d6105a63660046135f4565b6113fc565b61046d6105b936600461361e565b61149a565b61046d6105cc366004613686565b6115c9565b6104776118b0565b6104cb600f5481565b6104776118bd565b6104776118ca565b61046d61060036600461350e565b6118d7565b61046d6106133660046133a5565b611966565b61046d6106263660046135db565b611ce8565b61046d610639366004613583565b611d37565b610477611d8d565b600b546104459060ff1681565b610477611d9a565b6104cb6106693660046135c0565b611da7565b61046d61067c3660046135c0565b611dc2565b6104cb60105481565b61046d6106983660046136dd565b611e2e565b6104cb6106ab3660046135c0565b600d6020526000908152604090205481565b61046d6106cb366004613583565b611f4b565b600054610497906001600160a01b031681565b61046d6106f1366004613583565b611fa1565b61046d610704366004613583565b611ff7565b60095461044590600160a81b900460ff1681565b61073061072b3660046135c0565b61204d565b60405161045193929190613777565b6104776120f8565b61046d610755366004613583565b612105565b61046d61076836600461350e565b61215b565b61046d61077b3660046136dd565b6121c3565b61044561078e3660046135f4565b6122d7565b61046d6107a1366004613583565b61239e565b60095461044590600160b01b900460ff1681565b61046d6107c8366004613583565b6123f4565b6104cb6107db36600461379f565b61244a565b61046d6107ee36600461361e565b612475565b61046d610801366004613686565b612550565b61046d61081436600461350e565b61262d565b61046d6108273660046137d2565b6126c0565b61046d61280f565b6104cb61084236600461379f565b600e60209081526000928352604080842090915290825290205481565b61046d61086d366004613547565b612891565b6104456108803660046136dd565b612a5f565b61046d610893366004613583565b612b83565b610477612bd9565b61046d6108ae366004613583565b612be6565b6104cb60135481565b61046d6108ca3660046135db565b612c3c565b61046d6108dd3660046135c0565b612cbb565b610477612da5565b61046d6108f83660046135c0565b612db2565b610477612e1e565b60095461044590600160a01b900460ff1681565b61046d61092736600461350e565b612e2b565b61093f61093a3660046135db565b612e93565b604080516001600160a01b0395861681529490931660208501529183015215156060820152608001610451565b61046d61097a366004613547565b612edc565b336001600160a01b0316600c838154811061099c5761099c61384b565b60009182526020909120600490910201546001600160a01b0316148015906109cf57506000546001600160a01b03163314155b15610a1157604080518082018252600681526531323038303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60405180910390fd5b600c8281548110610a2457610a2461384b565b6000918252602082206003600490920201015460ff1615159003610a765760408051808201825260068152651899181c181960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b600c8281548110610a8957610a8961384b565b90600052602060002090600402016002015460086000600c8581548110610ab257610ab261384b565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610aeb908490613877565b9091555050600c805483908110610b0457610b0461384b565b906000526020600020906004020160020154600d6000600c8581548110610b2d57610b2d61384b565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610b6690849061388a565b925050819055506000600c8381548110610b8257610b8261384b565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610bdf57610bdf61384b565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610c1057610c1061384b565b6000918252602090912060016004909202010154604051610c3d92916001600160a01b031690869061389d565b60405180910390a25050565b60018054610c56906138c9565b80601f0160208091040260200160405190810160405280929190818152602001828054610c82906138c9565b8015610ccf5780601f10610ca457610100808354040283529160200191610ccf565b820191906000526020600020905b815481529060010190602001808311610cb257829003601f168201915b505050505081565b6000838214610d1457604080518082018252600681526531323035303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6000805b85811015610d4e57848482818110610d3257610d3261384b565b9050602002013582610d449190613877565b9150600101610d18565b5080610d5933611da7565b1015610d935760408051808201825260068152651899181a981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6004546001600160a01b03163314610df45760095460408051808201909152600681526531323035303360d01b602082015290600160a81b900460ff161515600114610df25760405162461bcd60e51b8152600401610a089190613432565b505b606060006001935060005b87811015610eef57610e37898983818110610e1c57610e1c61384b565b9050602002016020810190610e3191906135c0565b3b151590565b15610e8c57610e85898983818110610e5157610e5161384b565b9050602002016020810190610e6691906135c0565b888884818110610e7857610e7861384b565b9050602002013585613004565b9150610ed8565b610ed5898983818110610ea157610ea161384b565b9050602002016020810190610eb691906135c0565b888884818110610ec857610ec861384b565b905060200201358561311f565b91505b811515600003610ee757600094505b600101610dff565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610f445760405162461bcd60e51b8152600401610a089190613432565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f906020015b60405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610fd75760405162461bcd60e51b8152600401610a089190613432565b5081610fe285611da7565b101561101c57604080518082018252600681526531323036303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6060833b156110e1576110388361103287611da7565b906132e3565b6001600160a01b0386166000908152600860205260409020556110648361105e86611da7565b906132f6565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a906110a990339088908790600401613903565b600060405180830381600087803b1580156110c357600080fd5b505af11580156110d7573d6000803e3d6000fd5b505050505061112e565b6110ee8361103287611da7565b6001600160a01b0386166000908152600860205260409020556111148361105e86611da7565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b0316600080516020613aaf8339815191528560405161116191815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146111bd5760405162461bcd60e51b8152600401610a089190613432565b5060176111ca828261397b565b5050565b601a8054610c56906138c9565b60095460408051808201909152600681526531323130303160d01b602082015290600160b01b900460ff1615156001146112285760405162461bcd60e51b8152600401610a089190613432565b50600954600160a01b900460ff1615156001036112f75760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa158015611291573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112b59190613a3b565b60408051808201909152600681526518991898181960d11b60208201529015156001146112f55760405162461bcd60e51b8152600401610a089190613432565b505b336000908152600a60205260409020828155600201611316828261397b565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c033838360405161134a93929190613903565b60405180910390a15050565b60118054610c56906138c9565b60058054610c56906138c9565b60198054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113c65760405162461bcd60e51b8152600401610a089190613432565b5060138190556040518181527fe451abe602c33e612ea61221b5a142f7fe3c044286e42c8340ffe7fa86a5859790602001610f82565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146114455760405162461bcd60e51b8152600401610a089190613432565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b1910161134a565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146114e35760405162461bcd60e51b8152600401610a089190613432565b50816114ee84611da7565b101561152857604080518082018252600681526531323136303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6115358261103285611da7565b6001600160a01b03841660009081526008602052604090205561155c8261105e868661244a565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af51602906115bb9086908690613a58565b60405180910390a350505050565b600b5460ff1615806115e55750600954600160a81b900460ff16155b806115f75750816115f533611da7565b105b1561163057604080518082018252600681526531323037303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6000546001600160a01b0384811691161480159061165c5750600954600160a01b900460ff1615156001145b1561171b5760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa1580156116b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116d99190613a3b565b6040805180820190915260068152651899181b981960d11b60208201529015156001146117195760405162461bcd60e51b8152600401610a089190613432565b505b336000908152600860205260408120805484929061173a90849061388a565b9091555050336000908152600d60205260408120805484929061175e908490613877565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a916118a291889088908890613a71565b60405180910390a250505050565b60068054610c56906138c9565b60178054610c56906138c9565b601b8054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119205760405162461bcd60e51b8152600401610a089190613432565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119af5760405162461bcd60e51b8152600401610a089190613432565b50600954600160a81b900460ff1615156000036119fa57604080518082018252600681526531323039303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b600c8281548110611a0d57611a0d61384b565b6000918252602082206003600490920201015460ff1615159003611a5f5760408051808201825260068152651899181c981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b600c8281548110611a7257611a7261384b565b90600052602060002090600402016002015460086000600c8581548110611a9b57611a9b61384b565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611ad8908490613877565b9091555050600c805483908110611af157611af161384b565b906000526020600020906004020160020154600d6000600c8581548110611b1a57611b1a61384b565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611b5390849061388a565b925050819055506000600c8381548110611b6f57611b6f61384b565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611bcc57611bcc61384b565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611bfd57611bfd61384b565b6000918252602090912060016004909202010154604051611c2a92916001600160a01b031690869061389d565b60405180910390a2600c8281548110611c4557611c4561384b565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c7957611c7961384b565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613aaf833981519152919086908110611cbb57611cbb61384b565b906000526020600020906004020160020154604051611cdc91815260200190565b60405180910390a35050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d315760405162461bcd60e51b8152600401610a089190613432565b50601055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d805760405162461bcd60e51b8152600401610a089190613432565b5060116111ca828261397b565b60128054610c56906138c9565b60168054610c56906138c9565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e0b5760405162461bcd60e51b8152600401610a089190613432565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e775760405162461bcd60e51b8152600401610a089190613432565b508483141580611e875750848114155b15611ec057604080518082018252600681526531323134303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60005b85811015611f4257611f3a878783818110611ee057611ee061384b565b9050602002016020810190611ef591906135c0565b868684818110611f0757611f0761384b565b9050602002016020810190611f1c91906135c0565b858585818110611f2e57611f2e61384b565b90506020020135612891565b600101611ec3565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611f945760405162461bcd60e51b8152600401610a089190613432565b5060196111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611fea5760405162461bcd60e51b8152600401610a089190613432565b5060066111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120405760405162461bcd60e51b8152600401610a089190613432565b50601b6111ca828261397b565b600a6020526000908152604090208054600182015460028301805492939192612075906138c9565b80601f01602080910402602001604051908101604052809291908181526020018280546120a1906138c9565b80156120ee5780601f106120c3576101008083540402835291602001916120ee565b820191906000526020600020905b8154815290600101906020018083116120d157829003601f168201915b5050505050905083565b60028054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461214e5760405162461bcd60e51b8152600401610a089190613432565b50601c6111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146121a45760405162461bcd60e51b8152600401610a089190613432565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461220c5760405162461bcd60e51b8152600401610a089190613432565b50848314158061221c5750848114155b1561225557604080518082018252600681526531323133303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60005b85811015611f42576122cf8787838181106122755761227561384b565b905060200201602081019061228a91906135c0565b86868481811061229c5761229c61384b565b90506020020160208101906122b191906135c0565b8585858181106122c3576122c361384b565b90506020020135612edc565b600101612258565b6000816122e333611da7565b10156040518060400160405280600681526020016531323034303160d01b815250906123225760405162461bcd60e51b8152600401610a089190613432565b506009546040805180820190915260068152651899181a181960d11b602082015290600160a81b900460ff1615156001146123705760405162461bcd60e51b8152600401610a089190613432565b506060833b1561238d57612385848483613004565b915050612398565b61238584848361311f565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146123e75760405162461bcd60e51b8152600401610a089190613432565b50601a6111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461243d5760405162461bcd60e51b8152600401610a089190613432565b5060056111ca828261397b565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b81612480338661244a565b10156124ba57604080518082018252600681526518991818981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6124c882611032338761244a565b336000908152600e602090815260408083206001600160a01b03891684529091529020556124f98261105e85611da7565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc906115bb90879087908790613903565b8161255a33611da7565b101561259457604080518082018252600681526518991818181960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6125a18261103233611da7565b336000818152600860205260409020919091556125c590839061105e90869061244a565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f513906126209086908690613a58565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126765760405162461bcd60e51b8152600401610a089190613432565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146127095760405162461bcd60e51b8152600401610a089190613432565b5081612715868661244a565b101561274f57604080518082018252600681526531323132303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b61275d82611032878761244a565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556127908261105e85611da7565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161280093929190613903565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128585760405162461bcd60e51b8152600401610a089190613432565b506018805460ff191660011790556040517f96151d7f1d8c50be29a45c0e3ae9ce2008888d1615b196e8f5ffd8c178e6dde890600090a1565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128da5760405162461bcd60e51b8152600401610a089190613432565b506001600160a01b0382161561297857806128f5838561244a565b101561292f57604080518082018252600681526531323131303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b61293d81611032848661244a565b6001600160a01b038084166000908152600e602090815260408083209388168352929052205560035461297090826132e3565b600355612a03565b6001600160a01b0383166000908152600860205260409020548111156129cc57604080518082018252600681526518991898981960d11b6020820152905162461bcd60e51b8152610a089190600401613432565b6129d98161103285611da7565b6001600160a01b0384166000908152600860205260409020556003546129ff90826132e3565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef084604051612a5291815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612aa95760405162461bcd60e51b8152600401610a089190613432565b508584141580612ab95750858214155b15612af257604080518082018252600681526531323135303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b60005b86811015612b7557612b6c888883818110612b1257612b1261384b565b9050602002016020810190612b2791906135c0565b878784818110612b3957612b3961384b565b9050602002016020810190612b4e91906135c0565b868685818110612b6057612b6061384b565b90506020020135610f8d565b50600101612af5565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612bcc5760405162461bcd60e51b8152600401610a089190613432565b5060166111ca828261397b565b601c8054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c2f5760405162461bcd60e51b8152600401610a089190613432565b5060126111ca828261397b565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c855760405162461bcd60e51b8152600401610a089190613432565b50600f8190556040518181527f82b19c9a00131a87eeff1903fcb9e4f2a3ba76dd86098586eea921a533e218dc90602001610f82565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612d045760405162461bcd60e51b8152600401610a089190613432565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612d495760405162461bcd60e51b8152600401610a089190613432565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60158054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612dfb5760405162461bcd60e51b8152600401610a089190613432565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60148054610c56906138c9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612e745760405162461bcd60e51b8152600401610a089190613432565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c8181548110612ea357600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612f255760405162461bcd60e51b8152600401610a089190613432565b506001600160a01b03821615612f7e57612f438161105e848661244a565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612f7690826132f6565b600355612fb5565b612f8b8161105e85611da7565b6001600160a01b038416600090815260086020526040902055600354612fb190826132f6565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc84604051612a5291815260200190565b60045460408051808201909152600681526531323033303160d01b60208201526000916001600160a01b038681169116146130525760405162461bcd60e51b8152600401610a089190613432565b506130608361103233611da7565b3360009081526008602052604090205561307d8361105e86611da7565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a906130c290339088908890600401613903565b600060405180830381600087803b1580156130dc57600080fd5b505af11580156130f0573d6000803e3d6000fd5b50506040518681526001600160a01b0388169250339150600080516020613aaf83398151915290602001611161565b6004546000906001600160a01b031633148015906131445750600b5460ff1615156001145b1561317d57604080518082018252600681526531323032303160d01b6020820152905162461bcd60e51b8152610a089190600401613432565b6000546001600160a01b038581169116148015906131a95750600954600160a01b900460ff1615156001145b156132685760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015613202573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906132269190613a3b565b60408051808201909152600681526518991819181960d11b60208201529015156001146132665760405162461bcd60e51b8152600401610a089190613432565b505b6132758361103233611da7565b336000908152600860205260409020556132928361105e86611da7565b6001600160a01b038516600081815260086020526040908190209290925590513390600080516020613aaf833981519152906132d19087815260200190565b60405180910390a35060019392505050565b60006132ef828461388a565b9392505050565b60006132ef8284613877565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261332957600080fd5b813567ffffffffffffffff8082111561334457613344613302565b604051601f8301601f19908116603f0116810190828211818310171561336c5761336c613302565b8160405283815286602085880101111561338557600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080604083850312156133b857600080fd5b82359150602083013567ffffffffffffffff8111156133d657600080fd5b6133e285828601613318565b9150509250929050565b6000815180845260005b81811015613412576020818501810151868301820152016133f6565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006132ef60208301846133ec565b60008083601f84011261345757600080fd5b50813567ffffffffffffffff81111561346f57600080fd5b6020830191508360208260051b850101111561348a57600080fd5b9250929050565b600080600080604085870312156134a757600080fd5b843567ffffffffffffffff808211156134bf57600080fd5b6134cb88838901613445565b909650945060208701359150808211156134e457600080fd5b506134f187828801613445565b95989497509550505050565b801515811461350b57600080fd5b50565b60006020828403121561352057600080fd5b81356132ef816134fd565b80356001600160a01b038116811461354257600080fd5b919050565b60008060006060848603121561355c57600080fd5b6135658461352b565b92506135736020850161352b565b9150604084013590509250925092565b60006020828403121561359557600080fd5b813567ffffffffffffffff8111156135ac57600080fd5b6135b884828501613318565b949350505050565b6000602082840312156135d257600080fd5b6132ef8261352b565b6000602082840312156135ed57600080fd5b5035919050565b6000806040838503121561360757600080fd5b6136108361352b565b946020939093013593505050565b6000806000806080858703121561363457600080fd5b61363d8561352b565b935061364b6020860161352b565b925060408501359150606085013567ffffffffffffffff81111561366e57600080fd5b61367a87828801613318565b91505092959194509250565b60008060006060848603121561369b57600080fd5b6136a48461352b565b925060208401359150604084013567ffffffffffffffff8111156136c757600080fd5b6136d386828701613318565b9150509250925092565b600080600080600080606087890312156136f657600080fd5b863567ffffffffffffffff8082111561370e57600080fd5b61371a8a838b01613445565b9098509650602089013591508082111561373357600080fd5b61373f8a838b01613445565b9096509450604089013591508082111561375857600080fd5b5061376589828a01613445565b979a9699509497509295939492505050565b83815282602082015260606040820152600061379660608301846133ec565b95945050505050565b600080604083850312156137b257600080fd5b6137bb8361352b565b91506137c96020840161352b565b90509250929050565b600080600080600060a086880312156137ea57600080fd5b6137f38661352b565b94506138016020870161352b565b935061380f6040870161352b565b925060608601359150608086013567ffffffffffffffff81111561383257600080fd5b61383e88828901613318565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8082018082111561239857612398613861565b8181038181111561239857612398613861565b6001600160a01b03848116825283166020820152606060408201819052600090613796908301846133ec565b600181811c908216806138dd57607f821691505b6020821081036138fd57634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b038416815282602082015260606040820152600061379660608301846133ec565b601f821115613976576000816000526020600020601f850160051c810160208610156139535750805b601f850160051c820191505b818110156139725782815560010161395f565b5050505b505050565b815167ffffffffffffffff81111561399557613995613302565b6139a9816139a384546138c9565b8461392a565b602080601f8311600181146139de57600084156139c65750858301515b600019600386901b1c1916600185901b178555613972565b600085815260208120601f198616915b82811015613a0d578886015182559484019460019091019084016139ee565b5085821015613a2b5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215613a4d57600080fd5b81516132ef816134fd565b8281526040602082015260006135b860408301846133ec565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090613aa4908301846133ec565b969550505050505056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212205603470ddfb0f44164cb04123d9c86fdce49166a29e6c4eb3e468b682d39d3df64736f6c63430008170033" + "bytecode": "60806040523480156200001157600080fd5b506040516200416f3803806200416f8339810160408190526200003491620001f5565b600080546001600160a01b0319908116339182161717905560016200005a8c8262000431565b506002620000698b8262000431565b506003899055600f889055601962000082888262000431565b50601262000091878262000431565b506013859055601b620000a5858262000431565b506014620000b4848262000431565b506015620000c3838262000431565b506016620000d2828262000431565b5050600354600080546001600160a01b031681526008602052604090205550506018805460ff1990811690915560078054909116600117905550506009805460ff60a01b1916600160a01b17905550620004fd945050505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200015557600080fd5b81516001600160401b03808211156200017257620001726200012d565b604051601f8301601f19908116603f011681019082821181831017156200019d576200019d6200012d565b8160405283815260209250866020858801011115620001bb57600080fd5b600091505b83821015620001df5785820183015181830184015290820190620001c0565b6000602085830101528094505050505092915050565b60008060008060008060008060008060006101608c8e0312156200021857600080fd5b8b516001600160401b038111156200022f57600080fd5b6200023d8e828f0162000143565b60208e0151909c5090506001600160401b038111156200025c57600080fd5b6200026a8e828f0162000143565b9a505060408c0151985060608c0151975060808c015160018060401b038111156200029457600080fd5b620002a28e828f0162000143565b60a08e015190985090506001600160401b03811115620002c157600080fd5b620002cf8e828f0162000143565b60c08e015160e08f0151919850965090506001600160401b03811115620002f557600080fd5b620003038e828f0162000143565b6101008e015190955090506001600160401b038111156200032357600080fd5b620003318e828f0162000143565b6101208e015190945090506001600160401b038111156200035157600080fd5b6200035f8e828f0162000143565b6101408e015190935090506001600160401b038111156200037f57600080fd5b6200038d8e828f0162000143565b9150509295989b509295989b9093969950565b600181811c90821680620003b557607f821691505b602082108103620003d657634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200042c576000816000526020600020601f850160051c81016020861015620004075750805b601f850160051c820191505b81811015620004285782815560010162000413565b5050505b505050565b81516001600160401b038111156200044d576200044d6200012d565b62000465816200045e8454620003a0565b84620003dc565b602080601f8311600181146200049d5760008415620004845750858301515b600019600386901b1c1916600185901b17855562000428565b600085815260208120601f198616915b82811015620004ce57888601518255948401946001909101908401620004ad565b5085821015620004ed5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b613c62806200050d6000396000f3fe608060405234801561001057600080fd5b506004361061043e5760003560e01c80638ac08bf111610236578063ca5cbbb51161013b578063ef4474cd116100c3578063fc590d0911610087578063fc590d091461091b578063fd0e856914610923578063fd14a22214610937578063fef1b9c01461094a578063ff12dcea1461098a57600080fd5b8063ef4474cd146108d1578063ef8032ef146108da578063f2fde38b146108ed578063f35eeaef14610900578063f7502a7c1461090857600080fd5b8063e7464db11161010a578063e7464db11461087d578063e805005214610890578063eb68757f146108a3578063ed62c2a0146108b6578063ee0cfa40146108be57600080fd5b8063ca5cbbb514610824578063d5d9d0c714610837578063d87f09861461083f578063db20266f1461085257600080fd5b80639d8886c8116101be578063b967a52e1161018d578063b967a52e146107c5578063b9b3e06a146107d8578063bfe30fb0146107eb578063c267ce5f146107fe578063c7109ec91461081157600080fd5b80639d8886c814610778578063a9059cbb1461078b578063ab62f92e1461079e578063b2a90a60146107b157600080fd5b806392ff0d311161020557806392ff0d31146107145780639301a78b1461072857806395d89b411461074a57806397e4c513146107525780639cd237071461076557600080fd5b80638ac08bf1146106c85780638da5cb5b146106db5780638ed8a9bc146106ee578063919fe52d1461070157600080fd5b806340615cf8116103475780635f84f302116102cf57806370a082311161029357806370a082311461066657806376fa7a31146106795780637c3a00fd1461068c5780637ed6d1031461069557806381ebdd79146106a857600080fd5b80635f84f3021461062357806363783444146106365780636666e49c146106495780636f3b993f1461065157806370740aab1461065e57600080fd5b806344fd9caa1161031657806344fd9caa146105e457806358c3b870146105ed57806359d93617146105f55780635c40f6f4146105fd5780635ccef3e71461061057600080fd5b806340615cf8146105a357806340d1c441146105b657806340eba90e146105c9578063428630f6146105dc57600080fd5b806325287d42116103ca57806331cef4471161039957806331cef44714610565578063329dbddc1461057857806336f7ab5e146105805780633a2b6939146105885780633b18b9841461059057600080fd5b806325287d421461051757806325d608611461052a57806327e235e31461053d5780632e0ad0041461055d57600080fd5b8063153a1f3e11610411578063153a1f3e146104ba57806318160ddd146104cd5780631935a880146104e4578063200d2ed2146104f757806323b872dd1461050457600080fd5b8063034f6b211461044357806306eaa0b71461046557806306fdde031461047a5780630af7eb0f1461048f575b600080fd5b6018546104509060ff1681565b60405190151581526020015b60405180910390f35b610478610473366004613503565b61099d565b005b610482610c67565b60405161045c9190613590565b6009546104a2906001600160a01b031681565b6040516001600160a01b03909116815260200161045c565b6104506104c83660046135ef565b610cf5565b6104d660035481565b60405190815260200161045c565b6104786104f236600461366c565b610f19565b6007546104509060ff1681565b6104506105123660046136a5565b610fab565b6004546104a2906001600160a01b031681565b6104786105383660046136e1565b611192565b6104d661054b36600461371e565b60086020526000908152604090205481565b6104826111ec565b610478610573366004613503565b6111f9565b610482611374565b610482611381565b61048261138e565b61047861059e366004613739565b61139b565b6104786105b1366004613752565b61141a565b6104786105c436600461377c565b6114b8565b6104786105d73660046137e4565b6115e7565b6104826118ce565b6104d6600f5481565b6104826118db565b6104826118e8565b61047861060b36600461366c565b6118f5565b61047861061e366004613503565b611984565b610478610631366004613739565b611d06565b6104786106443660046136e1565b611d55565b610482611dab565b600b546104509060ff1681565b610482611db8565b6104d661067436600461371e565b611dc5565b61047861068736600461371e565b611de0565b6104d660105481565b6104786106a336600461383b565b611e4c565b6104d66106b636600461371e565b600d6020526000908152604090205481565b6104786106d63660046136e1565b611f69565b6000546104a2906001600160a01b031681565b6104786106fc3660046136e1565b611fbf565b61047861070f3660046136e1565b612015565b60095461045090600160a81b900460ff1681565b61073b61073636600461371e565b61206b565b60405161045c939291906138d5565b610482612116565b6104786107603660046136e1565b612123565b61047861077336600461366c565b612179565b61047861078636600461383b565b6121e1565b610450610799366004613752565b6122f5565b6104786107ac3660046136e1565b6123bc565b60095461045090600160b01b900460ff1681565b6104786107d33660046136e1565b612412565b6104d66107e63660046138fd565b612468565b6104786107f936600461377c565b612493565b61047861080c3660046137e4565b61256e565b61047861081f36600461366c565b61264b565b610478610832366004613930565b6126de565b61047861282d565b61047861084d366004613930565b6128af565b6104d66108603660046138fd565b600e60209081526000928352604080842090915290825290205481565b61047861088b3660046136a5565b6129ef565b61045061089e36600461383b565b612bbd565b6104786108b13660046136e1565b612ce1565b610482612d37565b6104786108cc3660046136e1565b612d44565b6104d660135481565b6104786108e8366004613739565b612d9a565b6104786108fb36600461371e565b612e19565b610482612f03565b61047861091636600461371e565b612f10565b610482612f7c565b60095461045090600160a01b900460ff1681565b61047861094536600461366c565b612f89565b61095d610958366004613739565b612ff1565b604080516001600160a01b039586168152949093166020850152918301521515606082015260800161045c565b6104786109983660046136a5565b61303a565b336001600160a01b0316600c83815481106109ba576109ba6139a9565b60009182526020909120600490910201546001600160a01b0316148015906109ed57506000546001600160a01b03163314155b15610a2f57604080518082018252600681526531323038303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60405180910390fd5b600c8281548110610a4257610a426139a9565b6000918252602082206003600490920201015460ff1615159003610a945760408051808201825260068152651899181c181960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b600c8281548110610aa757610aa76139a9565b90600052602060002090600402016002015460086000600c8581548110610ad057610ad06139a9565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610b099084906139d5565b9091555050600c805483908110610b2257610b226139a9565b906000526020600020906004020160020154600d6000600c8581548110610b4b57610b4b6139a9565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610b849084906139e8565b925050819055506000600c8381548110610ba057610ba06139a9565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610bfd57610bfd6139a9565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610c2e57610c2e6139a9565b6000918252602090912060016004909202010154604051610c5b92916001600160a01b03169086906139fb565b60405180910390a25050565b60018054610c7490613a27565b80601f0160208091040260200160405190810160405280929190818152602001828054610ca090613a27565b8015610ced5780601f10610cc257610100808354040283529160200191610ced565b820191906000526020600020905b815481529060010190602001808311610cd057829003601f168201915b505050505081565b6000838214610d3257604080518082018252600681526531323035303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6000805b85811015610d6c57848482818110610d5057610d506139a9565b9050602002013582610d6291906139d5565b9150600101610d36565b5080610d7733611dc5565b1015610db15760408051808201825260068152651899181a981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b6004546001600160a01b03163314610e125760095460408051808201909152600681526531323035303360d01b602082015290600160a81b900460ff161515600114610e105760405162461bcd60e51b8152600401610a269190613590565b505b606060006001935060005b87811015610f0d57610e55898983818110610e3a57610e3a6139a9565b9050602002016020810190610e4f919061371e565b3b151590565b15610eaa57610ea3898983818110610e6f57610e6f6139a9565b9050602002016020810190610e84919061371e565b888884818110610e9657610e966139a9565b9050602002013585613162565b9150610ef6565b610ef3898983818110610ebf57610ebf6139a9565b9050602002016020810190610ed4919061371e565b888884818110610ee657610ee66139a9565b905060200201358561327d565b91505b811515600003610f0557600094505b600101610e1d565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610f625760405162461bcd60e51b8152600401610a269190613590565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f906020015b60405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610ff55760405162461bcd60e51b8152600401610a269190613590565b508161100085611dc5565b101561103a57604080518082018252600681526531323036303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6060833b156110ff576110568361105087611dc5565b90613441565b6001600160a01b0386166000908152600860205260409020556110828361107c86611dc5565b90613454565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a906110c790339088908790600401613a61565b600060405180830381600087803b1580156110e157600080fd5b505af11580156110f5573d6000803e3d6000fd5b505050505061114c565b61110c8361105087611dc5565b6001600160a01b0386166000908152600860205260409020556111328361107c86611dc5565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b0316600080516020613c0d8339815191528560405161117f91815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146111db5760405162461bcd60e51b8152600401610a269190613590565b5060176111e88282613ad9565b5050565b601a8054610c7490613a27565b60095460408051808201909152600681526531323130303160d01b602082015290600160b01b900460ff1615156001146112465760405162461bcd60e51b8152600401610a269190613590565b50600954600160a01b900460ff1615156001036113155760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa1580156112af573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112d39190613b99565b60408051808201909152600681526518991898181960d11b60208201529015156001146113135760405162461bcd60e51b8152600401610a269190613590565b505b336000908152600a602052604090208281556002016113348282613ad9565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c033838360405161136893929190613a61565b60405180910390a15050565b60118054610c7490613a27565b60058054610c7490613a27565b60198054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113e45760405162461bcd60e51b8152600401610a269190613590565b5060138190556040518181527fe451abe602c33e612ea61221b5a142f7fe3c044286e42c8340ffe7fa86a5859790602001610fa0565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146114635760405162461bcd60e51b8152600401610a269190613590565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b19101611368565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146115015760405162461bcd60e51b8152600401610a269190613590565b508161150c84611dc5565b101561154657604080518082018252600681526531323136303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6115538261105085611dc5565b6001600160a01b03841660009081526008602052604090205561157a8261107c8686612468565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af51602906115d99086908690613bb6565b60405180910390a350505050565b600b5460ff1615806116035750600954600160a81b900460ff16155b8061161557508161161333611dc5565b105b1561164e57604080518082018252600681526531323037303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6000546001600160a01b0384811691161480159061167a5750600954600160a01b900460ff1615156001145b156117395760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa1580156116d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116f79190613b99565b6040805180820190915260068152651899181b981960d11b60208201529015156001146117375760405162461bcd60e51b8152600401610a269190613590565b505b33600090815260086020526040812080548492906117589084906139e8565b9091555050336000908152600d60205260408120805484929061177c9084906139d5565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a916118c091889088908890613bcf565b60405180910390a250505050565b60068054610c7490613a27565b60178054610c7490613a27565b601b8054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461193e5760405162461bcd60e51b8152600401610a269190613590565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119cd5760405162461bcd60e51b8152600401610a269190613590565b50600954600160a81b900460ff161515600003611a1857604080518082018252600681526531323039303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b600c8281548110611a2b57611a2b6139a9565b6000918252602082206003600490920201015460ff1615159003611a7d5760408051808201825260068152651899181c981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b600c8281548110611a9057611a906139a9565b90600052602060002090600402016002015460086000600c8581548110611ab957611ab96139a9565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611af69084906139d5565b9091555050600c805483908110611b0f57611b0f6139a9565b906000526020600020906004020160020154600d6000600c8581548110611b3857611b386139a9565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611b719084906139e8565b925050819055506000600c8381548110611b8d57611b8d6139a9565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611bea57611bea6139a9565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611c1b57611c1b6139a9565b6000918252602090912060016004909202010154604051611c4892916001600160a01b03169086906139fb565b60405180910390a2600c8281548110611c6357611c636139a9565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c9757611c976139a9565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613c0d833981519152919086908110611cd957611cd96139a9565b906000526020600020906004020160020154604051611cfa91815260200190565b60405180910390a35050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d4f5760405162461bcd60e51b8152600401610a269190613590565b50601055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d9e5760405162461bcd60e51b8152600401610a269190613590565b5060116111e88282613ad9565b60128054610c7490613a27565b60168054610c7490613a27565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e295760405162461bcd60e51b8152600401610a269190613590565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e955760405162461bcd60e51b8152600401610a269190613590565b508483141580611ea55750848114155b15611ede57604080518082018252600681526531323134303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60005b85811015611f6057611f58878783818110611efe57611efe6139a9565b9050602002016020810190611f13919061371e565b868684818110611f2557611f256139a9565b9050602002016020810190611f3a919061371e565b858585818110611f4c57611f4c6139a9565b905060200201356129ef565b600101611ee1565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611fb25760405162461bcd60e51b8152600401610a269190613590565b5060196111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120085760405162461bcd60e51b8152600401610a269190613590565b5060066111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461205e5760405162461bcd60e51b8152600401610a269190613590565b50601b6111e88282613ad9565b600a602052600090815260409020805460018201546002830180549293919261209390613a27565b80601f01602080910402602001604051908101604052809291908181526020018280546120bf90613a27565b801561210c5780601f106120e15761010080835404028352916020019161210c565b820191906000526020600020905b8154815290600101906020018083116120ef57829003601f168201915b5050505050905083565b60028054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461216c5760405162461bcd60e51b8152600401610a269190613590565b50601c6111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146121c25760405162461bcd60e51b8152600401610a269190613590565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461222a5760405162461bcd60e51b8152600401610a269190613590565b50848314158061223a5750848114155b1561227357604080518082018252600681526531323133303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60005b85811015611f60576122ed878783818110612293576122936139a9565b90506020020160208101906122a8919061371e565b8686848181106122ba576122ba6139a9565b90506020020160208101906122cf919061371e565b8585858181106122e1576122e16139a9565b9050602002013561303a565b600101612276565b60008161230133611dc5565b10156040518060400160405280600681526020016531323034303160d01b815250906123405760405162461bcd60e51b8152600401610a269190613590565b506009546040805180820190915260068152651899181a181960d11b602082015290600160a81b900460ff16151560011461238e5760405162461bcd60e51b8152600401610a269190613590565b506060833b156123ab576123a3848483613162565b9150506123b6565b6123a384848361327d565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146124055760405162461bcd60e51b8152600401610a269190613590565b50601a6111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461245b5760405162461bcd60e51b8152600401610a269190613590565b5060056111e88282613ad9565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b8161249e3386612468565b10156124d857604080518082018252600681526518991818981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b6124e6826110503387612468565b336000908152600e602090815260408083206001600160a01b03891684529091529020556125178261107c85611dc5565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc906115d990879087908790613a61565b8161257833611dc5565b10156125b257604080518082018252600681526518991818181960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b6125bf8261105033611dc5565b336000818152600860205260409020919091556125e390839061107c908690612468565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f5139061263e9086908690613bb6565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126945760405162461bcd60e51b8152600401610a269190613590565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146127275760405162461bcd60e51b8152600401610a269190613590565b50816127338686612468565b101561276d57604080518082018252600681526531323132303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b61277b826110508787612468565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556127ae8261107c85611dc5565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161281e93929190613a61565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128765760405162461bcd60e51b8152600401610a269190613590565b506018805460ff191660011790556040517f96151d7f1d8c50be29a45c0e3ae9ce2008888d1615b196e8f5ffd8c178e6dde890600090a1565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128f85760405162461bcd60e51b8152600401610a269190613590565b50816129048686612468565b101561293e57604080518082018252600681526531323137303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b61294c826110508787612468565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556129808261107c8786612468565b6001600160a01b038087166000818152600e602090815260408083208986168085529252918290209490945551918716917f5847ffcc1bce0ecfdd115863db8562df2b18b61698938e251adf381774e9e3be906129e09087908790613bb6565b60405180910390a45050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612a385760405162461bcd60e51b8152600401610a269190613590565b506001600160a01b03821615612ad65780612a538385612468565b1015612a8d57604080518082018252600681526531323131303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b612a9b816110508486612468565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612ace9082613441565b600355612b61565b6001600160a01b038316600090815260086020526040902054811115612b2a57604080518082018252600681526518991898981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b612b378161105085611dc5565b6001600160a01b038416600090815260086020526040902055600354612b5d9082613441565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef084604051612bb091815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c075760405162461bcd60e51b8152600401610a269190613590565b508584141580612c175750858214155b15612c5057604080518082018252600681526531323135303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60005b86811015612cd357612cca888883818110612c7057612c706139a9565b9050602002016020810190612c85919061371e565b878784818110612c9757612c976139a9565b9050602002016020810190612cac919061371e565b868685818110612cbe57612cbe6139a9565b90506020020135610fab565b50600101612c53565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612d2a5760405162461bcd60e51b8152600401610a269190613590565b5060166111e88282613ad9565b601c8054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612d8d5760405162461bcd60e51b8152600401610a269190613590565b5060126111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612de35760405162461bcd60e51b8152600401610a269190613590565b50600f8190556040518181527f82b19c9a00131a87eeff1903fcb9e4f2a3ba76dd86098586eea921a533e218dc90602001610fa0565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612e625760405162461bcd60e51b8152600401610a269190613590565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612ea75760405162461bcd60e51b8152600401610a269190613590565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60158054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612f595760405162461bcd60e51b8152600401610a269190613590565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60148054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612fd25760405162461bcd60e51b8152600401610a269190613590565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c818154811061300157600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146130835760405162461bcd60e51b8152600401610a269190613590565b506001600160a01b038216156130dc576130a18161107c8486612468565b6001600160a01b038084166000908152600e60209081526040808320938816835292905220556003546130d49082613454565b600355613113565b6130e98161107c85611dc5565b6001600160a01b03841660009081526008602052604090205560035461310f9082613454565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc84604051612bb091815260200190565b60045460408051808201909152600681526531323033303160d01b60208201526000916001600160a01b038681169116146131b05760405162461bcd60e51b8152600401610a269190613590565b506131be8361105033611dc5565b336000908152600860205260409020556131db8361107c86611dc5565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a9061322090339088908890600401613a61565b600060405180830381600087803b15801561323a57600080fd5b505af115801561324e573d6000803e3d6000fd5b50506040518681526001600160a01b0388169250339150600080516020613c0d8339815191529060200161117f565b6004546000906001600160a01b031633148015906132a25750600b5460ff1615156001145b156132db57604080518082018252600681526531323032303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6000546001600160a01b038581169116148015906133075750600954600160a01b900460ff1615156001145b156133c65760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015613360573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133849190613b99565b60408051808201909152600681526518991819181960d11b60208201529015156001146133c45760405162461bcd60e51b8152600401610a269190613590565b505b6133d38361105033611dc5565b336000908152600860205260409020556133f08361107c86611dc5565b6001600160a01b038516600081815260086020526040908190209290925590513390600080516020613c0d8339815191529061342f9087815260200190565b60405180910390a35060019392505050565b600061344d82846139e8565b9392505050565b600061344d82846139d5565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261348757600080fd5b813567ffffffffffffffff808211156134a2576134a2613460565b604051601f8301601f19908116603f011681019082821181831017156134ca576134ca613460565b816040528381528660208588010111156134e357600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561351657600080fd5b82359150602083013567ffffffffffffffff81111561353457600080fd5b61354085828601613476565b9150509250929050565b6000815180845260005b8181101561357057602081850181015186830182015201613554565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061344d602083018461354a565b60008083601f8401126135b557600080fd5b50813567ffffffffffffffff8111156135cd57600080fd5b6020830191508360208260051b85010111156135e857600080fd5b9250929050565b6000806000806040858703121561360557600080fd5b843567ffffffffffffffff8082111561361d57600080fd5b613629888389016135a3565b9096509450602087013591508082111561364257600080fd5b5061364f878288016135a3565b95989497509550505050565b801515811461366957600080fd5b50565b60006020828403121561367e57600080fd5b813561344d8161365b565b80356001600160a01b03811681146136a057600080fd5b919050565b6000806000606084860312156136ba57600080fd5b6136c384613689565b92506136d160208501613689565b9150604084013590509250925092565b6000602082840312156136f357600080fd5b813567ffffffffffffffff81111561370a57600080fd5b61371684828501613476565b949350505050565b60006020828403121561373057600080fd5b61344d82613689565b60006020828403121561374b57600080fd5b5035919050565b6000806040838503121561376557600080fd5b61376e83613689565b946020939093013593505050565b6000806000806080858703121561379257600080fd5b61379b85613689565b93506137a960208601613689565b925060408501359150606085013567ffffffffffffffff8111156137cc57600080fd5b6137d887828801613476565b91505092959194509250565b6000806000606084860312156137f957600080fd5b61380284613689565b925060208401359150604084013567ffffffffffffffff81111561382557600080fd5b61383186828701613476565b9150509250925092565b6000806000806000806060878903121561385457600080fd5b863567ffffffffffffffff8082111561386c57600080fd5b6138788a838b016135a3565b9098509650602089013591508082111561389157600080fd5b61389d8a838b016135a3565b909650945060408901359150808211156138b657600080fd5b506138c389828a016135a3565b979a9699509497509295939492505050565b8381528260208201526060604082015260006138f4606083018461354a565b95945050505050565b6000806040838503121561391057600080fd5b61391983613689565b915061392760208401613689565b90509250929050565b600080600080600060a0868803121561394857600080fd5b61395186613689565b945061395f60208701613689565b935061396d60408701613689565b925060608601359150608086013567ffffffffffffffff81111561399057600080fd5b61399c88828901613476565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156123b6576123b66139bf565b818103818111156123b6576123b66139bf565b6001600160a01b038481168252831660208201526060604082018190526000906138f49083018461354a565b600181811c90821680613a3b57607f821691505b602082108103613a5b57634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b03841681528260208201526060604082015260006138f4606083018461354a565b601f821115613ad4576000816000526020600020601f850160051c81016020861015613ab15750805b601f850160051c820191505b81811015613ad057828155600101613abd565b5050505b505050565b815167ffffffffffffffff811115613af357613af3613460565b613b0781613b018454613a27565b84613a88565b602080601f831160018114613b3c5760008415613b245750858301515b600019600386901b1c1916600185901b178555613ad0565b600085815260208120601f198616915b82811015613b6b57888601518255948401946001909101908401613b4c565b5085821015613b895787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215613bab57600080fd5b815161344d8161365b565b828152604060208201526000613716604083018461354a565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090613c029083018461354a565b969550505050505056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa264697066735822122061defbbe8b73a970d84efe79630b66e96950d98e87fcc71249e9eba64f55024464736f6c63430008170033", + "deployedBytecode": "608060405234801561001057600080fd5b506004361061043e5760003560e01c80638ac08bf111610236578063ca5cbbb51161013b578063ef4474cd116100c3578063fc590d0911610087578063fc590d091461091b578063fd0e856914610923578063fd14a22214610937578063fef1b9c01461094a578063ff12dcea1461098a57600080fd5b8063ef4474cd146108d1578063ef8032ef146108da578063f2fde38b146108ed578063f35eeaef14610900578063f7502a7c1461090857600080fd5b8063e7464db11161010a578063e7464db11461087d578063e805005214610890578063eb68757f146108a3578063ed62c2a0146108b6578063ee0cfa40146108be57600080fd5b8063ca5cbbb514610824578063d5d9d0c714610837578063d87f09861461083f578063db20266f1461085257600080fd5b80639d8886c8116101be578063b967a52e1161018d578063b967a52e146107c5578063b9b3e06a146107d8578063bfe30fb0146107eb578063c267ce5f146107fe578063c7109ec91461081157600080fd5b80639d8886c814610778578063a9059cbb1461078b578063ab62f92e1461079e578063b2a90a60146107b157600080fd5b806392ff0d311161020557806392ff0d31146107145780639301a78b1461072857806395d89b411461074a57806397e4c513146107525780639cd237071461076557600080fd5b80638ac08bf1146106c85780638da5cb5b146106db5780638ed8a9bc146106ee578063919fe52d1461070157600080fd5b806340615cf8116103475780635f84f302116102cf57806370a082311161029357806370a082311461066657806376fa7a31146106795780637c3a00fd1461068c5780637ed6d1031461069557806381ebdd79146106a857600080fd5b80635f84f3021461062357806363783444146106365780636666e49c146106495780636f3b993f1461065157806370740aab1461065e57600080fd5b806344fd9caa1161031657806344fd9caa146105e457806358c3b870146105ed57806359d93617146105f55780635c40f6f4146105fd5780635ccef3e71461061057600080fd5b806340615cf8146105a357806340d1c441146105b657806340eba90e146105c9578063428630f6146105dc57600080fd5b806325287d42116103ca57806331cef4471161039957806331cef44714610565578063329dbddc1461057857806336f7ab5e146105805780633a2b6939146105885780633b18b9841461059057600080fd5b806325287d421461051757806325d608611461052a57806327e235e31461053d5780632e0ad0041461055d57600080fd5b8063153a1f3e11610411578063153a1f3e146104ba57806318160ddd146104cd5780631935a880146104e4578063200d2ed2146104f757806323b872dd1461050457600080fd5b8063034f6b211461044357806306eaa0b71461046557806306fdde031461047a5780630af7eb0f1461048f575b600080fd5b6018546104509060ff1681565b60405190151581526020015b60405180910390f35b610478610473366004613503565b61099d565b005b610482610c67565b60405161045c9190613590565b6009546104a2906001600160a01b031681565b6040516001600160a01b03909116815260200161045c565b6104506104c83660046135ef565b610cf5565b6104d660035481565b60405190815260200161045c565b6104786104f236600461366c565b610f19565b6007546104509060ff1681565b6104506105123660046136a5565b610fab565b6004546104a2906001600160a01b031681565b6104786105383660046136e1565b611192565b6104d661054b36600461371e565b60086020526000908152604090205481565b6104826111ec565b610478610573366004613503565b6111f9565b610482611374565b610482611381565b61048261138e565b61047861059e366004613739565b61139b565b6104786105b1366004613752565b61141a565b6104786105c436600461377c565b6114b8565b6104786105d73660046137e4565b6115e7565b6104826118ce565b6104d6600f5481565b6104826118db565b6104826118e8565b61047861060b36600461366c565b6118f5565b61047861061e366004613503565b611984565b610478610631366004613739565b611d06565b6104786106443660046136e1565b611d55565b610482611dab565b600b546104509060ff1681565b610482611db8565b6104d661067436600461371e565b611dc5565b61047861068736600461371e565b611de0565b6104d660105481565b6104786106a336600461383b565b611e4c565b6104d66106b636600461371e565b600d6020526000908152604090205481565b6104786106d63660046136e1565b611f69565b6000546104a2906001600160a01b031681565b6104786106fc3660046136e1565b611fbf565b61047861070f3660046136e1565b612015565b60095461045090600160a81b900460ff1681565b61073b61073636600461371e565b61206b565b60405161045c939291906138d5565b610482612116565b6104786107603660046136e1565b612123565b61047861077336600461366c565b612179565b61047861078636600461383b565b6121e1565b610450610799366004613752565b6122f5565b6104786107ac3660046136e1565b6123bc565b60095461045090600160b01b900460ff1681565b6104786107d33660046136e1565b612412565b6104d66107e63660046138fd565b612468565b6104786107f936600461377c565b612493565b61047861080c3660046137e4565b61256e565b61047861081f36600461366c565b61264b565b610478610832366004613930565b6126de565b61047861282d565b61047861084d366004613930565b6128af565b6104d66108603660046138fd565b600e60209081526000928352604080842090915290825290205481565b61047861088b3660046136a5565b6129ef565b61045061089e36600461383b565b612bbd565b6104786108b13660046136e1565b612ce1565b610482612d37565b6104786108cc3660046136e1565b612d44565b6104d660135481565b6104786108e8366004613739565b612d9a565b6104786108fb36600461371e565b612e19565b610482612f03565b61047861091636600461371e565b612f10565b610482612f7c565b60095461045090600160a01b900460ff1681565b61047861094536600461366c565b612f89565b61095d610958366004613739565b612ff1565b604080516001600160a01b039586168152949093166020850152918301521515606082015260800161045c565b6104786109983660046136a5565b61303a565b336001600160a01b0316600c83815481106109ba576109ba6139a9565b60009182526020909120600490910201546001600160a01b0316148015906109ed57506000546001600160a01b03163314155b15610a2f57604080518082018252600681526531323038303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60405180910390fd5b600c8281548110610a4257610a426139a9565b6000918252602082206003600490920201015460ff1615159003610a945760408051808201825260068152651899181c181960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b600c8281548110610aa757610aa76139a9565b90600052602060002090600402016002015460086000600c8581548110610ad057610ad06139a9565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610b099084906139d5565b9091555050600c805483908110610b2257610b226139a9565b906000526020600020906004020160020154600d6000600c8581548110610b4b57610b4b6139a9565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190610b849084906139e8565b925050819055506000600c8381548110610ba057610ba06139a9565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817fa0cc3a4feb26502e6e6cd57b2113d58bdab80c6711d8252356be9a49945bb5f7600c8481548110610bfd57610bfd6139a9565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110610c2e57610c2e6139a9565b6000918252602090912060016004909202010154604051610c5b92916001600160a01b03169086906139fb565b60405180910390a25050565b60018054610c7490613a27565b80601f0160208091040260200160405190810160405280929190818152602001828054610ca090613a27565b8015610ced5780601f10610cc257610100808354040283529160200191610ced565b820191906000526020600020905b815481529060010190602001808311610cd057829003601f168201915b505050505081565b6000838214610d3257604080518082018252600681526531323035303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6000805b85811015610d6c57848482818110610d5057610d506139a9565b9050602002013582610d6291906139d5565b9150600101610d36565b5080610d7733611dc5565b1015610db15760408051808201825260068152651899181a981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b6004546001600160a01b03163314610e125760095460408051808201909152600681526531323035303360d01b602082015290600160a81b900460ff161515600114610e105760405162461bcd60e51b8152600401610a269190613590565b505b606060006001935060005b87811015610f0d57610e55898983818110610e3a57610e3a6139a9565b9050602002016020810190610e4f919061371e565b3b151590565b15610eaa57610ea3898983818110610e6f57610e6f6139a9565b9050602002016020810190610e84919061371e565b888884818110610e9657610e966139a9565b9050602002013585613162565b9150610ef6565b610ef3898983818110610ebf57610ebf6139a9565b9050602002016020810190610ed4919061371e565b888884818110610ee657610ee66139a9565b905060200201358561327d565b91505b811515600003610f0557600094505b600101610e1d565b50505050949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610f625760405162461bcd60e51b8152600401610a269190613590565b50600b805460ff19168215159081179091556040519081527fe1455bc53682229469d6919bb96d7b338cbffea93170dfb5f02242c22fb6d07f906020015b60405180910390a150565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314610ff55760405162461bcd60e51b8152600401610a269190613590565b508161100085611dc5565b101561103a57604080518082018252600681526531323036303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6060833b156110ff576110568361105087611dc5565b90613441565b6001600160a01b0386166000908152600860205260409020556110828361107c86611dc5565b90613454565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a906110c790339088908790600401613a61565b600060405180830381600087803b1580156110e157600080fd5b505af11580156110f5573d6000803e3d6000fd5b505050505061114c565b61110c8361105087611dc5565b6001600160a01b0386166000908152600860205260409020556111328361107c86611dc5565b6001600160a01b0385166000908152600860205260409020555b836001600160a01b0316856001600160a01b0316600080516020613c0d8339815191528560405161117f91815260200190565b60405180910390a3506001949350505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146111db5760405162461bcd60e51b8152600401610a269190613590565b5060176111e88282613ad9565b5050565b601a8054610c7490613a27565b60095460408051808201909152600681526531323130303160d01b602082015290600160b01b900460ff1615156001146112465760405162461bcd60e51b8152600401610a269190613590565b50600954600160a01b900460ff1615156001036113155760095460005460405163d3da927f60e01b81523360048201526001600160a01b03918216602482015291169063d3da927f90604401602060405180830381865afa1580156112af573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112d39190613b99565b60408051808201909152600681526518991898181960d11b60208201529015156001146113135760405162461bcd60e51b8152600401610a269190613590565b505b336000908152600a602052604090208281556002016113348282613ad9565b507f3bb9553f633a7ecb4cbc5f9885e750a522496ce06deff1ed9184180ca83940c033838360405161136893929190613a61565b60405180910390a15050565b60118054610c7490613a27565b60058054610c7490613a27565b60198054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146113e45760405162461bcd60e51b8152600401610a269190613590565b5060138190556040518181527fe451abe602c33e612ea61221b5a142f7fe3c044286e42c8340ffe7fa86a5859790602001610fa0565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146114635760405162461bcd60e51b8152600401610a269190613590565b506001600160a01b0382166000818152600a6020908152604091829020600101849055815192835282018390527fa42bd6bdfcb8f9b815c68e4ce0e16dec3f1e7ac53967ee8309653de522a735b19101611368565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146115015760405162461bcd60e51b8152600401610a269190613590565b508161150c84611dc5565b101561154657604080518082018252600681526531323136303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6115538261105085611dc5565b6001600160a01b03841660009081526008602052604090205561157a8261107c8686612468565b6001600160a01b038086166000818152600e6020908152604080832094891680845294909152908190209390935591517fd7bb89baba75d47cbd55a36bae224d3c630a8d3114860f86a30b484b2af51602906115d99086908690613bb6565b60405180910390a350505050565b600b5460ff1615806116035750600954600160a81b900460ff16155b8061161557508161161333611dc5565b105b1561164e57604080518082018252600681526531323037303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6000546001600160a01b0384811691161480159061167a5750600954600160a01b900460ff1615156001145b156117395760095460005460405163d3da927f60e01b81526001600160a01b038681166004830152918216602482015291169063d3da927f90604401602060405180830381865afa1580156116d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116f79190613b99565b6040805180820190915260068152651899181b981960d11b60208201529015156001146117375760405162461bcd60e51b8152600401610a269190613590565b505b33600090815260086020526040812080548492906117589084906139e8565b9091555050336000908152600d60205260408120805484929061177c9084906139d5565b9091555050600c805460408051608081018252338082526001600160a01b038881166020840190815283850189815260016060860181815290880189556000989098529351600487027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549285166001600160a01b031993841617905591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8830180549190941691161790915591517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c983015593517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8ca909101805491151560ff1990921691909117905551909182917fa256c3bc72c26fbfd6285cfde8c7e345b4d0c7ccfc725d103e9f129c5d8a039a916118c091889088908890613bcf565b60405180910390a250505050565b60068054610c7490613a27565b60178054610c7490613a27565b601b8054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461193e5760405162461bcd60e51b8152600401610a269190613590565b506007805460ff191682151590811790915560405160ff9091161515907fe1e5c6f5867805bf3fab73f4df4b80d0e87e9262a7bc22ac7c2cb3fd5896222f90600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146119cd5760405162461bcd60e51b8152600401610a269190613590565b50600954600160a81b900460ff161515600003611a1857604080518082018252600681526531323039303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b600c8281548110611a2b57611a2b6139a9565b6000918252602082206003600490920201015460ff1615159003611a7d5760408051808201825260068152651899181c981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b600c8281548110611a9057611a906139a9565b90600052602060002090600402016002015460086000600c8581548110611ab957611ab96139a9565b60009182526020808320600160049093020191909101546001600160a01b0316835282019290925260400181208054909190611af69084906139d5565b9091555050600c805483908110611b0f57611b0f6139a9565b906000526020600020906004020160020154600d6000600c8581548110611b3857611b386139a9565b600091825260208083206004909202909101546001600160a01b0316835282019290925260400181208054909190611b719084906139e8565b925050819055506000600c8381548110611b8d57611b8d6139a9565b906000526020600020906004020160030160006101000a81548160ff021916908315150217905550817f6b50d6e4941ce7ae74ed7f7f51df5fb7680754c2815a094c66bea5ca80a40eb1600c8481548110611bea57611bea6139a9565b6000918252602090912060049091020154600c80546001600160a01b039092169186908110611c1b57611c1b6139a9565b6000918252602090912060016004909202010154604051611c4892916001600160a01b03169086906139fb565b60405180910390a2600c8281548110611c6357611c636139a9565b6000918252602090912060016004909202010154600c80546001600160a01b039092169184908110611c9757611c976139a9565b6000918252602090912060049091020154600c80546001600160a01b0390921691600080516020613c0d833981519152919086908110611cd957611cd96139a9565b906000526020600020906004020160020154604051611cfa91815260200190565b60405180910390a35050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d4f5760405162461bcd60e51b8152600401610a269190613590565b50601055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611d9e5760405162461bcd60e51b8152600401610a269190613590565b5060116111e88282613ad9565b60128054610c7490613a27565b60168054610c7490613a27565b6001600160a01b031660009081526008602052604090205490565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e295760405162461bcd60e51b8152600401610a269190613590565b50600480546001600160a01b0319166001600160a01b0392909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611e955760405162461bcd60e51b8152600401610a269190613590565b508483141580611ea55750848114155b15611ede57604080518082018252600681526531323134303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60005b85811015611f6057611f58878783818110611efe57611efe6139a9565b9050602002016020810190611f13919061371e565b868684818110611f2557611f256139a9565b9050602002016020810190611f3a919061371e565b858585818110611f4c57611f4c6139a9565b905060200201356129ef565b600101611ee1565b50505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314611fb25760405162461bcd60e51b8152600401610a269190613590565b5060196111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146120085760405162461bcd60e51b8152600401610a269190613590565b5060066111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461205e5760405162461bcd60e51b8152600401610a269190613590565b50601b6111e88282613ad9565b600a602052600090815260409020805460018201546002830180549293919261209390613a27565b80601f01602080910402602001604051908101604052809291908181526020018280546120bf90613a27565b801561210c5780601f106120e15761010080835404028352916020019161210c565b820191906000526020600020905b8154815290600101906020018083116120ef57829003601f168201915b5050505050905083565b60028054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461216c5760405162461bcd60e51b8152600401610a269190613590565b50601c6111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146121c25760405162461bcd60e51b8152600401610a269190613590565b5060098054911515600160a81b0260ff60a81b19909216919091179055565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461222a5760405162461bcd60e51b8152600401610a269190613590565b50848314158061223a5750848114155b1561227357604080518082018252600681526531323133303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60005b85811015611f60576122ed878783818110612293576122936139a9565b90506020020160208101906122a8919061371e565b8686848181106122ba576122ba6139a9565b90506020020160208101906122cf919061371e565b8585858181106122e1576122e16139a9565b9050602002013561303a565b600101612276565b60008161230133611dc5565b10156040518060400160405280600681526020016531323034303160d01b815250906123405760405162461bcd60e51b8152600401610a269190613590565b506009546040805180820190915260068152651899181a181960d11b602082015290600160a81b900460ff16151560011461238e5760405162461bcd60e51b8152600401610a269190613590565b506060833b156123ab576123a3848483613162565b9150506123b6565b6123a384848361327d565b92915050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146124055760405162461bcd60e51b8152600401610a269190613590565b50601a6111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b0316331461245b5760405162461bcd60e51b8152600401610a269190613590565b5060056111e88282613ad9565b6001600160a01b039182166000908152600e6020908152604080832093909416825291909152205490565b8161249e3386612468565b10156124d857604080518082018252600681526518991818981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b6124e6826110503387612468565b336000908152600e602090815260408083206001600160a01b03891684529091529020556125178261107c85611dc5565b6001600160a01b038085166000908152600860205260409081902092909255905133918616907f7a1a1088c929a111dda2dc0ca1cc650aa78525d11fd3089abf20c8474f5d82cc906115d990879087908790613a61565b8161257833611dc5565b10156125b257604080518082018252600681526518991818181960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b6125bf8261105033611dc5565b336000818152600860205260409020919091556125e390839061107c908690612468565b6001600160a01b0384166000818152600e602090815260408083203380855292529182902093909355519091907ecd77abc69e7bbb6de9df8cbfcca8720df3a30decf9eefd187e72ab2ea2f5139061263e9086908690613bb6565b60405180910390a3505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146126945760405162461bcd60e51b8152600401610a269190613590565b506009805460ff60b01b1916600160b01b831515908102919091179091556040517fa0c691180b8089c0c84e478ee67d6325768666cd889f13de016771c6f7af1f8890600090a250565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146127275760405162461bcd60e51b8152600401610a269190613590565b50816127338686612468565b101561276d57604080518082018252600681526531323132303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b61277b826110508787612468565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556127ae8261107c85611dc5565b60086000856001600160a01b03166001600160a01b0316815260200190815260200160002081905550846001600160a01b0316846001600160a01b03167f66b661491f3fe4e137b9e9cf90e142a93be21bc10e70c9b740379a245e4f7d2585858560405161281e93929190613a61565b60405180910390a35050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128765760405162461bcd60e51b8152600401610a269190613590565b506018805460ff191660011790556040517f96151d7f1d8c50be29a45c0e3ae9ce2008888d1615b196e8f5ffd8c178e6dde890600090a1565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146128f85760405162461bcd60e51b8152600401610a269190613590565b50816129048686612468565b101561293e57604080518082018252600681526531323137303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b61294c826110508787612468565b6001600160a01b038087166000908152600e60209081526040808320938916835292905220556129808261107c8786612468565b6001600160a01b038087166000818152600e602090815260408083208986168085529252918290209490945551918716917f5847ffcc1bce0ecfdd115863db8562df2b18b61698938e251adf381774e9e3be906129e09087908790613bb6565b60405180910390a45050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612a385760405162461bcd60e51b8152600401610a269190613590565b506001600160a01b03821615612ad65780612a538385612468565b1015612a8d57604080518082018252600681526531323131303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b612a9b816110508486612468565b6001600160a01b038084166000908152600e6020908152604080832093881683529290522055600354612ace9082613441565b600355612b61565b6001600160a01b038316600090815260086020526040902054811115612b2a57604080518082018252600681526518991898981960d11b6020820152905162461bcd60e51b8152610a269190600401613590565b612b378161105085611dc5565b6001600160a01b038416600090815260086020526040902055600354612b5d9082613441565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167fee02732fab40ece8284c756220846dff4b8d32058b86b35b4f0459bf172fcef084604051612bb091815260200190565b60405180910390a4505050565b6000805460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612c075760405162461bcd60e51b8152600401610a269190613590565b508584141580612c175750858214155b15612c5057604080518082018252600681526531323135303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b60005b86811015612cd357612cca888883818110612c7057612c706139a9565b9050602002016020810190612c85919061371e565b878784818110612c9757612c976139a9565b9050602002016020810190612cac919061371e565b868685818110612cbe57612cbe6139a9565b90506020020135610fab565b50600101612c53565b506001979650505050505050565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612d2a5760405162461bcd60e51b8152600401610a269190613590565b5060166111e88282613ad9565b601c8054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612d8d5760405162461bcd60e51b8152600401610a269190613590565b5060126111e88282613ad9565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612de35760405162461bcd60e51b8152600401610a269190613590565b50600f8190556040518181527f82b19c9a00131a87eeff1903fcb9e4f2a3ba76dd86098586eea921a533e218dc90602001610fa0565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612e625760405162461bcd60e51b8152600401610a269190613590565b5060408051808201909152600681526535303031303160d01b60208201526001600160a01b038216612ea75760405162461bcd60e51b8152600401610a269190613590565b50600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60158054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612f595760405162461bcd60e51b8152600401610a269190613590565b50600980546001600160a01b0319166001600160a01b0392909216919091179055565b60148054610c7490613a27565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b03163314612fd25760405162461bcd60e51b8152600401610a269190613590565b5060098054911515600160a01b0260ff60a01b19909216919091179055565b600c818154811061300157600080fd5b600091825260209091206004909102018054600182015460028301546003909301546001600160a01b0392831694509116919060ff1684565b60005460408051808201909152600681526535303030303160d01b6020820152906001600160a01b031633146130835760405162461bcd60e51b8152600401610a269190613590565b506001600160a01b038216156130dc576130a18161107c8486612468565b6001600160a01b038084166000908152600e60209081526040808320938816835292905220556003546130d49082613454565b600355613113565b6130e98161107c85611dc5565b6001600160a01b03841660009081526008602052604090205560035461310f9082613454565b6003555b816001600160a01b0316836001600160a01b0316336001600160a01b03167f65adeb76912378393e600cb6f64f3310842a42e1eae86273dc775d0c47a0f2dc84604051612bb091815260200190565b60045460408051808201909152600681526531323033303160d01b60208201526000916001600160a01b038681169116146131b05760405162461bcd60e51b8152600401610a269190613590565b506131be8361105033611dc5565b336000908152600860205260409020556131db8361107c86611dc5565b6001600160a01b0385166000818152600860205260409081902092909255905163607705c560e11b815285919063c0ee0b8a9061322090339088908890600401613a61565b600060405180830381600087803b15801561323a57600080fd5b505af115801561324e573d6000803e3d6000fd5b50506040518681526001600160a01b0388169250339150600080516020613c0d8339815191529060200161117f565b6004546000906001600160a01b031633148015906132a25750600b5460ff1615156001145b156132db57604080518082018252600681526531323032303160d01b6020820152905162461bcd60e51b8152610a269190600401613590565b6000546001600160a01b038581169116148015906133075750600954600160a01b900460ff1615156001145b156133c65760095460005460405163d3da927f60e01b81526001600160a01b038781166004830152918216602482015291169063d3da927f90604401602060405180830381865afa158015613360573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133849190613b99565b60408051808201909152600681526518991819181960d11b60208201529015156001146133c45760405162461bcd60e51b8152600401610a269190613590565b505b6133d38361105033611dc5565b336000908152600860205260409020556133f08361107c86611dc5565b6001600160a01b038516600081815260086020526040908190209290925590513390600080516020613c0d8339815191529061342f9087815260200190565b60405180910390a35060019392505050565b600061344d82846139e8565b9392505050565b600061344d82846139d5565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261348757600080fd5b813567ffffffffffffffff808211156134a2576134a2613460565b604051601f8301601f19908116603f011681019082821181831017156134ca576134ca613460565b816040528381528660208588010111156134e357600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561351657600080fd5b82359150602083013567ffffffffffffffff81111561353457600080fd5b61354085828601613476565b9150509250929050565b6000815180845260005b8181101561357057602081850181015186830182015201613554565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061344d602083018461354a565b60008083601f8401126135b557600080fd5b50813567ffffffffffffffff8111156135cd57600080fd5b6020830191508360208260051b85010111156135e857600080fd5b9250929050565b6000806000806040858703121561360557600080fd5b843567ffffffffffffffff8082111561361d57600080fd5b613629888389016135a3565b9096509450602087013591508082111561364257600080fd5b5061364f878288016135a3565b95989497509550505050565b801515811461366957600080fd5b50565b60006020828403121561367e57600080fd5b813561344d8161365b565b80356001600160a01b03811681146136a057600080fd5b919050565b6000806000606084860312156136ba57600080fd5b6136c384613689565b92506136d160208501613689565b9150604084013590509250925092565b6000602082840312156136f357600080fd5b813567ffffffffffffffff81111561370a57600080fd5b61371684828501613476565b949350505050565b60006020828403121561373057600080fd5b61344d82613689565b60006020828403121561374b57600080fd5b5035919050565b6000806040838503121561376557600080fd5b61376e83613689565b946020939093013593505050565b6000806000806080858703121561379257600080fd5b61379b85613689565b93506137a960208601613689565b925060408501359150606085013567ffffffffffffffff8111156137cc57600080fd5b6137d887828801613476565b91505092959194509250565b6000806000606084860312156137f957600080fd5b61380284613689565b925060208401359150604084013567ffffffffffffffff81111561382557600080fd5b61383186828701613476565b9150509250925092565b6000806000806000806060878903121561385457600080fd5b863567ffffffffffffffff8082111561386c57600080fd5b6138788a838b016135a3565b9098509650602089013591508082111561389157600080fd5b61389d8a838b016135a3565b909650945060408901359150808211156138b657600080fd5b506138c389828a016135a3565b979a9699509497509295939492505050565b8381528260208201526060604082015260006138f4606083018461354a565b95945050505050565b6000806040838503121561391057600080fd5b61391983613689565b915061392760208401613689565b90509250929050565b600080600080600060a0868803121561394857600080fd5b61395186613689565b945061395f60208701613689565b935061396d60408701613689565b925060608601359150608086013567ffffffffffffffff81111561399057600080fd5b61399c88828901613476565b9150509295509295909350565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156123b6576123b66139bf565b818103818111156123b6576123b66139bf565b6001600160a01b038481168252831660208201526060604082018190526000906138f49083018461354a565b600181811c90821680613a3b57607f821691505b602082108103613a5b57634e487b7160e01b600052602260045260246000fd5b50919050565b60018060a01b03841681528260208201526060604082015260006138f4606083018461354a565b601f821115613ad4576000816000526020600020601f850160051c81016020861015613ab15750805b601f850160051c820191505b81811015613ad057828155600101613abd565b5050505b505050565b815167ffffffffffffffff811115613af357613af3613460565b613b0781613b018454613a27565b84613a88565b602080601f831160018114613b3c5760008415613b245750858301515b600019600386901b1c1916600185901b178555613ad0565b600085815260208120601f198616915b82811015613b6b57888601518255948401946001909101908401613b4c565b5085821015613b895787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215613bab57600080fd5b815161344d8161365b565b828152604060208201526000613716604083018461354a565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090613c029083018461354a565b969550505050505056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa264697066735822122061defbbe8b73a970d84efe79630b66e96950d98e87fcc71249e9eba64f55024464736f6c63430008170033" } \ No newline at end of file diff --git a/app/model/db/idx_transfer.py b/app/model/db/idx_transfer.py index 1ff4d54f6..16d87f058 100644 --- a/app/model/db/idx_transfer.py +++ b/app/model/db/idx_transfer.py @@ -39,6 +39,7 @@ class IDXTransferSourceEventType(StrEnum): TRANSFER = "Transfer" UNLOCK = "Unlock" FORCE_UNLOCK = "ForceUnlock" + FORCE_CHANGE_LOCKED_ACCOUNT = "ForceChangeLockedAccount" class DataMessage(BaseModel): diff --git a/app/utils/contract_error_code.py b/app/utils/contract_error_code.py index 3ccc239e5..6d3ff49d9 100644 --- a/app/utils/contract_error_code.py +++ b/app/utils/contract_error_code.py @@ -68,6 +68,7 @@ def error_code_msg(code_str: str) -> Tuple[int, str]: 111401: "The length of the target address, the lock address and the amount don't match.", 111501: "The From, To and Value lengths don't match.", 111601: "Lock amount is greater than message sender balance.", + 111701: "Locked balance is not sufficient.", # IbetStraightBond (12XXXX) 120001: "Lock address is invalid.", # < v22.12 120002: "Lock amount is greater than message sender balance.", @@ -97,6 +98,7 @@ def error_code_msg(code_str: str) -> Tuple[int, str]: 121401: "The length of the target address, the lock address and the amount don't match.", 121501: "The From, To and Value lengths don't match.", 121601: "Lock amount is greater than message sender balance.", + 121701: "Locked balance is not sufficient.", # IbetCoupon (13XXXX) 130001: "Destination address isn't tradable exchange.", 130101: "Message sender balance is insufficient.", diff --git a/batch/indexer_Position_Bond.py b/batch/indexer_Position_Bond.py index 4ace26ab0..80592bff0 100644 --- a/batch/indexer_Position_Bond.py +++ b/batch/indexer_Position_Bond.py @@ -22,7 +22,7 @@ import sys from datetime import datetime, timedelta, timezone from itertools import groupby -from typing import List, Optional, Sequence +from typing import List, Literal, Optional, Sequence from eth_utils import to_checksum_address from sqlalchemy import select @@ -300,6 +300,7 @@ async def __sync_all(self, db_session: AsyncSession, block_to: int): await self.__sync_force_lock(db_session, block_to) await self.__sync_unlock(db_session, block_to) await self.__sync_force_unlock(db_session, block_to) + await self.__sync_force_change_locked_account(db_session, block_to) await self.__sync_issue(db_session, block_to) await self.__sync_redeem(db_session, block_to) await self.__sync_apply_for_transfer(db_session, block_to) @@ -718,6 +719,112 @@ async def __sync_force_unlock(self, db_session: AsyncSession, block_to: int): except Exception as e: raise e + async def __sync_force_change_locked_account( + self, db_session: AsyncSession, block_to: int + ): + """Sync ForceChangeLockedAccount Events + + :param db_session: ORM session + :param block_to: To block + :return: None + """ + for target in self.token_list: + token = target.token_contract + block_from = target.cursor + if block_from > block_to: + continue + try: + events = await token.events.ForceChangeLockedAccount.get_logs( + from_block=block_from, to_block=block_to + ) + except ABIEventNotFound: + events = [] + try: + lock_map: dict[str, dict[str, Literal[True]]] = {} + for event in events: + args = event["args"] + lock_address = args.get("lockAddress", "") + before_account_address = args.get("beforeAccountAddress", "") + after_account_address = args.get("afterAccountAddress", "") + value = args.get("value", 0) + data = args.get("data", "") + event_created = await self.__gen_block_timestamp(event=event) + tx = await async_web3.eth.get_transaction(event["transactionHash"]) + msg_sender = tx["from"] + + # Index Unlock event + # - Set after_account as recipient + self.__insert_unlock_idx( + db_session=db_session, + transaction_hash=event["transactionHash"].to_0x_hex(), + msg_sender=msg_sender, + block_number=event["blockNumber"], + token_address=token.address, + lock_address=lock_address, + account_address=before_account_address, + recipient_address=after_account_address, + value=value, + data_str=data, + block_timestamp=event_created, + is_forced=True, + ) + + # Index Lock event + self.__insert_lock_idx( + db_session=db_session, + transaction_hash=event["transactionHash"].to_0x_hex(), + msg_sender=msg_sender, + block_number=event["blockNumber"], + token_address=token.address, + lock_address=lock_address, + account_address=after_account_address, + value=value, + data_str=data, + block_timestamp=event_created, + is_forced=True, + ) + + if lock_address not in lock_map: + lock_map[lock_address] = {} + lock_map[lock_address][before_account_address] = True + lock_map[lock_address][after_account_address] = True + + for lock_address in lock_map: + for account_address in lock_map[lock_address]: + # Update Locked Position + value = await self.__get_account_locked_token( + token, lock_address, account_address + ) + await self.__sink_on_locked_position( + db_session=db_session, + token_address=to_checksum_address(token.address), + lock_address=lock_address, + account_address=account_address, + value=value, + ) + except Exception: + pass + try: + accounts_filtered = self.remove_duplicate_event_by_token_account_desc( + events=events, + account_keys=["beforeAccountAddress", "afterAccountAddress"], + ) + balances_list = await self.get_bulk_account_balance_token( + token=token, + accounts=accounts_filtered, + ) + for balances in balances_list: + (account, balance, pending_transfer) = balances + await self.__sink_on_position( + db_session=db_session, + token_address=to_checksum_address(token.address), + account_address=account, + balance=balance, + pending_transfer=pending_transfer, + ) + except Exception as e: + raise e + async def __sync_issue(self, db_session: AsyncSession, block_to: int): """Sync Issue Events diff --git a/batch/indexer_Position_Share.py b/batch/indexer_Position_Share.py index 4d009d02f..09a5de3c8 100644 --- a/batch/indexer_Position_Share.py +++ b/batch/indexer_Position_Share.py @@ -22,7 +22,7 @@ import sys from datetime import datetime, timedelta, timezone from itertools import groupby -from typing import List, Optional, Sequence +from typing import List, Literal, Optional, Sequence from eth_utils import to_checksum_address from sqlalchemy import select @@ -300,6 +300,7 @@ async def __sync_all(self, db_session: AsyncSession, block_to: int): await self.__sync_force_lock(db_session, block_to) await self.__sync_unlock(db_session, block_to) await self.__sync_force_unlock(db_session, block_to) + await self.__sync_force_change_locked_account(db_session, block_to) await self.__sync_issue(db_session, block_to) await self.__sync_redeem(db_session, block_to) await self.__sync_apply_for_transfer(db_session, block_to) @@ -718,6 +719,112 @@ async def __sync_force_unlock(self, db_session: AsyncSession, block_to: int): except Exception as e: raise e + async def __sync_force_change_locked_account( + self, db_session: AsyncSession, block_to: int + ): + """Sync ForceChangeLockedAccount Events + + :param db_session: ORM session + :param block_to: To block + :return: None + """ + for target in self.token_list: + token = target.token_contract + block_from = target.cursor + if block_from > block_to: + continue + try: + events = await token.events.ForceChangeLockedAccount.get_logs( + from_block=block_from, to_block=block_to + ) + except ABIEventNotFound: + events = [] + try: + lock_map: dict[str, dict[str, Literal[True]]] = {} + for event in events: + args = event["args"] + lock_address = args.get("lockAddress", "") + before_account_address = args.get("beforeAccountAddress", "") + after_account_address = args.get("afterAccountAddress", "") + value = args.get("value", 0) + data = args.get("data", "") + event_created = await self.__gen_block_timestamp(event=event) + tx = await async_web3.eth.get_transaction(event["transactionHash"]) + msg_sender = tx["from"] + + # Index Unlock event + # - Set after_account as recipient + self.__insert_unlock_idx( + db_session=db_session, + transaction_hash=event["transactionHash"].to_0x_hex(), + msg_sender=msg_sender, + block_number=event["blockNumber"], + token_address=token.address, + lock_address=lock_address, + account_address=before_account_address, + recipient_address=after_account_address, + value=value, + data_str=data, + block_timestamp=event_created, + is_forced=True, + ) + + # Index Lock event + self.__insert_lock_idx( + db_session=db_session, + transaction_hash=event["transactionHash"].to_0x_hex(), + msg_sender=msg_sender, + block_number=event["blockNumber"], + token_address=token.address, + lock_address=lock_address, + account_address=after_account_address, + value=value, + data_str=data, + block_timestamp=event_created, + is_forced=True, + ) + + if lock_address not in lock_map: + lock_map[lock_address] = {} + lock_map[lock_address][before_account_address] = True + lock_map[lock_address][after_account_address] = True + + for lock_address in lock_map: + for account_address in lock_map[lock_address]: + # Update Locked Position + value = await self.__get_account_locked_token( + token, lock_address, account_address + ) + await self.__sink_on_locked_position( + db_session=db_session, + token_address=to_checksum_address(token.address), + lock_address=lock_address, + account_address=account_address, + value=value, + ) + except Exception: + pass + try: + accounts_filtered = self.remove_duplicate_event_by_token_account_desc( + events=events, + account_keys=["beforeAccountAddress", "afterAccountAddress"], + ) + balances_list = await self.get_bulk_account_balance_token( + token=token, + accounts=accounts_filtered, + ) + for balances in balances_list: + (account, balance, pending_transfer) = balances + await self.__sink_on_position( + db_session=db_session, + token_address=to_checksum_address(token.address), + account_address=account, + balance=balance, + pending_transfer=pending_transfer, + ) + except Exception as e: + raise e + async def __sync_issue(self, db_session: AsyncSession, block_to: int): """Sync Issue Events diff --git a/batch/indexer_Token_Holders.py b/batch/indexer_Token_Holders.py index 19f01a315..162cff177 100644 --- a/batch/indexer_Token_Holders.py +++ b/batch/indexer_Token_Holders.py @@ -260,6 +260,7 @@ async def __process_all( await self.__process_force_lock(block_from, block_to) await self.__process_unlock(block_from, block_to) await self.__process_force_unlock(block_from, block_to) + await self.__process_force_change_locked_account(block_from, block_to) if self.token_template == TokenType.IbetCoupon: await self.__process_consume(block_from, block_to) @@ -579,6 +580,41 @@ async def __process_force_unlock(self, block_from: int, block_to: int): except Exception: raise + async def __process_force_change_locked_account( + self, block_from: int, block_to: int + ): + """Process ForceChangeLockedAccount Event + + - The process of updating Hold-Balance data by capturing the following events + - `ForceChangeLockedAccount` event on Token contracts + + :param block_from: From block + :param block_to: To block + :return: None + """ + try: + # Get "ForceChangeLockedAccount" events from token contract + events = await self.token_contract.events.ForceChangeLockedAccount.get_logs( + from_block=block_from, to_block=block_to + ) + except ABIEventNotFound: + events = [] + try: + for event in events: + args = event["args"] + before_account_address = args.get("beforeAccountAddress", ZERO_ADDRESS) + after_account_address = args.get("afterAccountAddress", ZERO_ADDRESS) + amount = args.get("value") + if amount is not None and amount <= sys.maxsize: + self.balance_book.store( + account_address=before_account_address, locked=-amount + ) + self.balance_book.store( + account_address=after_account_address, locked=+amount + ) + except Exception: + raise + @staticmethod async def __save_holders( db_session: AsyncSession, diff --git a/batch/indexer_Transfer.py b/batch/indexer_Transfer.py index e9d6a82af..47c6ee9b7 100644 --- a/batch/indexer_Transfer.py +++ b/batch/indexer_Transfer.py @@ -319,6 +319,7 @@ async def __sync_all( await self.__sync_transfer(db_session, block_from, block_to) await self.__sync_unlock(db_session, block_from, block_to) await self.__sync_force_unlock(db_session, block_from, block_to) + await self.__sync_force_change_locked_account(db_session, block_from, block_to) await self.__update_skip_block(db_session) async def __sync_transfer( @@ -525,6 +526,74 @@ async def __sync_force_unlock( except Exception: raise + async def __sync_force_change_locked_account( + self, db_session: AsyncSession, block_from: int, block_to: int + ): + """Synchronize ForceChangeLockedAccount events + + :param db_session: database session + :param block_from: from block number + :param block_to: to block number + :return: None + """ + for target in self.token_list: + token = target.token_contract + skip_block = target.skip_block + + # Get "ForceChangeLockedAccount" logs + try: + if skip_block is not None and block_to <= skip_block: + # Skip if the token has already been synchronized to block_to. + LOG.debug(f"{token.address}: block_to <= skip_block") + continue + elif skip_block is not None and block_from <= skip_block < block_to: + # block_from <= skip_block < block_to + LOG.debug(f"{token.address}: block_from <= skip_block < block_to") + events = await token.events.ForceChangeLockedAccount.get_logs( + from_block=skip_block + 1, to_block=block_to + ) + else: + # No logs or + # skip_block < block_from < block_to + LOG.debug(f"{token.address}: skip_block < block_from < block_to") + events = await token.events.ForceChangeLockedAccount.get_logs( + from_block=block_from, to_block=block_to + ) + except ABIEventNotFound: + events = [] + + # Index logs + try: + for event in events: + args = event["args"] + transaction_hash = event["transactionHash"].to_0x_hex() + block_timestamp = datetime.fromtimestamp( + (await async_web3.eth.get_block(event["blockNumber"]))[ + "timestamp" + ], + UTC, + ).replace(tzinfo=None) + if args["value"] > sys.maxsize: + pass + else: + from_address = args.get("beforeAccountAddress", ZERO_ADDRESS) + to_address = args.get("afterAccountAddress", ZERO_ADDRESS) + data_str = args.get("data", "") + if from_address != to_address: + self.__insert_idx( + db_session=db_session, + transaction_hash=transaction_hash, + token_address=to_checksum_address(token.address), + from_account_address=from_address, + to_account_address=to_address, + value=args["value"], + source_event=IDXTransferSourceEventType.FORCE_CHANGE_LOCKED_ACCOUNT, + data_str=data_str, + event_created=block_timestamp, + ) + except Exception: + raise + async def __update_skip_block(self, db_session: AsyncSession): """Memorize the block number where next processing should start from diff --git a/tests/batch/indexer_Position_Bond_test.py b/tests/batch/indexer_Position_Bond_test.py index 3e70d8955..afd8e891b 100644 --- a/tests/batch/indexer_Position_Bond_test.py +++ b/tests/batch/indexer_Position_Bond_test.py @@ -919,6 +919,138 @@ async def test_normal_5_2(self, processor, shared_contract, session): assert _unlock1.data == {"message": "unlocked1"} assert _unlock1.is_forced is True + # + # Single Token + # Single event logs + # - Transfer + # - Lock + # - ForceChangeLockedAccount + async def test_normal_5_3(self, processor, shared_contract, session): + # Issue Token + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + token = self.issue_token_bond( + self.issuer, + config.ZERO_ADDRESS, + personal_info_contract["address"], + token_list_contract, + ) + self.listing_token(token["address"], session) + + token_contract = Contract.get_contract("IbetStraightBond", token["address"]) + + PersonalInfoUtils.register( + self.trader["account_address"], + personal_info_contract["address"], + self.issuer["account_address"], + ) + + # Transfer + bond_transfer_to_exchange( + self.issuer, {"address": self.trader["account_address"]}, token, 3000 + ) + + # Lock + token_contract.functions.lock( + self.issuer["account_address"], # lock address + 3000, + '{"message": "locked1"}', + ).transact({"from": self.trader["account_address"]}) + + # ForceChangeLockedAccount + token_contract.functions.forceChangeLockedAccount( + self.issuer["account_address"], # lock address + self.trader["account_address"], # before account address + self.trader2["account_address"], # after account address + 100, + '{"message": "force_changed"}', + ).transact({"from": self.issuer["account_address"]}) + + # Run target process + block_number = web3.eth.block_number + await processor.sync_new_logs() + + # Assertion + _position_list: Sequence[IDXPosition] = session.scalars( + select(IDXPosition).order_by(IDXPosition.created) + ).all() + assert len(_position_list) == 1 + + _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( + select(IDXPositionBondBlockNumber) + .where(IDXPositionBondBlockNumber.token_address == token["address"]) + .limit(1) + ).first() + assert _idx_position_bond_block_number.latest_block_number == block_number + + _position: IDXPosition = session.scalars( + select(IDXPosition) + .where( + and_( + IDXPosition.token_address == token["address"], + IDXPosition.account_address == self.issuer["account_address"], + ) + ) + .limit(1) + ).first() + assert _position.token_address == token["address"] + assert _position.account_address == self.issuer["account_address"] + assert _position.balance == 1000000 - 3000 + assert _position.pending_transfer == 0 + assert _position.exchange_balance == 0 + assert _position.exchange_commitment == 0 + + _locked_list: Sequence[IDXLockedPosition] = session.scalars( + select(IDXLockedPosition).order_by(IDXLockedPosition.created) + ).all() + assert len(_locked_list) == 2 + _locked = _locked_list[0] + assert _locked.token_address == token["address"] + assert _locked.lock_address == self.issuer["account_address"] + assert _locked.account_address == self.trader["account_address"] + assert _locked.value == 2900 + _locked = _locked_list[1] + assert _locked.token_address == token["address"] + assert _locked.lock_address == self.issuer["account_address"] + assert _locked.account_address == self.trader2["account_address"] + assert _locked.value == 100 + + _lock_list: Sequence[IDXLock] = session.scalars( + select(IDXLock).order_by(IDXLock.id) + ).all() + assert len(_lock_list) == 2 + _lock1 = _lock_list[0] + assert _lock1.id == 1 + assert _lock1.token_address == token["address"] + assert _lock1.msg_sender == self.trader["account_address"] + assert _lock1.lock_address == self.issuer["account_address"] + assert _lock1.account_address == self.trader["account_address"] + assert _lock1.value == 3000 + assert _lock1.data == {"message": "locked1"} + _lock2 = _lock_list[1] + assert _lock2.id == 2 + assert _lock2.token_address == token["address"] + assert _lock2.msg_sender == self.issuer["account_address"] + assert _lock2.lock_address == self.issuer["account_address"] + assert _lock2.account_address == self.trader2["account_address"] + assert _lock2.value == 100 + assert _lock2.data == {"message": "force_changed"} + + _unlock_list: Sequence[IDXUnlock] = session.scalars( + select(IDXUnlock).order_by(IDXUnlock.id) + ).all() + assert len(_unlock_list) == 1 + _unlock1 = _unlock_list[0] + assert _unlock1.id == 1 + assert _unlock1.token_address == token["address"] + assert _unlock1.msg_sender == self.issuer["account_address"] + assert _unlock1.lock_address == self.issuer["account_address"] + assert _unlock1.account_address == self.trader["account_address"] + assert _unlock1.recipient_address == self.trader2["account_address"] + assert _unlock1.value == 100 + assert _unlock1.data == {"message": "force_changed"} + assert _unlock1.is_forced is True + # # Single Token # Single event logs diff --git a/tests/batch/indexer_Position_Share_test.py b/tests/batch/indexer_Position_Share_test.py index fedc834eb..0f3d62fd0 100644 --- a/tests/batch/indexer_Position_Share_test.py +++ b/tests/batch/indexer_Position_Share_test.py @@ -891,6 +891,133 @@ async def test_normal_5_2(self, processor, shared_contract, session): assert _unlock1.data == {"message": "unlocked1"} assert _unlock1.is_forced is True + # + # Single Token + # Single event logs + # - Transfer + # - Lock + # - ForceChangeLockedAccount + async def test_normal_5_3(self, processor, shared_contract, session): + # Issue Token + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + token = self.issue_token_share( + self.issuer, + config.ZERO_ADDRESS, + personal_info_contract["address"], + token_list_contract, + ) + self.listing_token(token["address"], session) + + token_contract = Contract.get_contract("IbetShare", token["address"]) + + PersonalInfoUtils.register( + self.trader["account_address"], + personal_info_contract["address"], + self.issuer["account_address"], + ) + # Transfer + share_transfer_to_exchange( + self.issuer, {"address": self.trader["account_address"]}, token, 3000 + ) + + # Lock + token_contract.functions.lock( + self.issuer["account_address"], # lock address + 3000, + '{"message": "locked1"}', + ).transact({"from": self.trader["account_address"]}) + + # ForceChangeLockedAccount + token_contract.functions.forceChangeLockedAccount( + self.issuer["account_address"], # lock address + self.trader["account_address"], # before account address + self.trader2["account_address"], # after account address + 100, + '{"message": "force_changed"}', + ).transact({"from": self.issuer["account_address"]}) + + # Run target process + block_number = web3.eth.block_number + await processor.sync_new_logs() + + # Assertion + _position_list: Sequence[IDXPosition] = session.scalars( + select(IDXPosition).order_by(IDXPosition.created) + ).all() + assert len(_position_list) == 1 + + _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( + select(IDXPositionShareBlockNumber) + .where(IDXPositionShareBlockNumber.token_address == token["address"]) + .limit(1) + ).first() + assert _idx_position_share_block_number.latest_block_number == block_number + + _position: IDXPosition = session.scalars( + select(IDXPosition) + .where( + and_( + IDXPosition.token_address == token["address"], + IDXPosition.account_address == self.issuer["account_address"], + ) + ) + .limit(1) + ).first() + assert _position.token_address == token["address"] + assert _position.account_address == self.issuer["account_address"] + assert _position.balance == 1000000 - 3000 + assert _position.pending_transfer == 0 + assert _position.exchange_balance == 0 + assert _position.exchange_commitment == 0 + + _locked_list = session.scalars( + select(IDXLockedPosition).order_by(IDXLockedPosition.created) + ).all() + assert len(_locked_list) == 2 + _locked = _locked_list[0] + assert _locked.token_address == token["address"] + assert _locked.lock_address == self.issuer["account_address"] + assert _locked.account_address == self.trader["account_address"] + assert _locked.value == 2900 + _locked = _locked_list[1] + assert _locked.token_address == token["address"] + assert _locked.lock_address == self.issuer["account_address"] + assert _locked.account_address == self.trader2["account_address"] + assert _locked.value == 100 + + _lock_list = session.scalars(select(IDXLock).order_by(IDXLock.id)).all() + assert len(_lock_list) == 2 + _lock1 = _lock_list[0] + assert _lock1.id == 1 + assert _lock1.token_address == token["address"] + assert _lock1.msg_sender == self.trader["account_address"] + assert _lock1.lock_address == self.issuer["account_address"] + assert _lock1.account_address == self.trader["account_address"] + assert _lock1.value == 3000 + assert _lock1.data == {"message": "locked1"} + _lock2 = _lock_list[1] + assert _lock2.id == 2 + assert _lock2.token_address == token["address"] + assert _lock2.msg_sender == self.issuer["account_address"] + assert _lock2.lock_address == self.issuer["account_address"] + assert _lock2.account_address == self.trader2["account_address"] + assert _lock2.value == 100 + assert _lock2.data == {"message": "force_changed"} + + _unlock_list = session.scalars(select(IDXUnlock).order_by(IDXUnlock.id)).all() + assert len(_unlock_list) == 1 + _unlock1 = _unlock_list[0] + assert _unlock1.id == 1 + assert _unlock1.token_address == token["address"] + assert _unlock1.msg_sender == self.issuer["account_address"] + assert _unlock1.lock_address == self.issuer["account_address"] + assert _unlock1.account_address == self.trader["account_address"] + assert _unlock1.recipient_address == self.trader2["account_address"] + assert _unlock1.value == 100 + assert _unlock1.data == {"message": "force_changed"} + assert _unlock1.is_forced is True + # # Single Token # Single event logs diff --git a/tests/batch/indexer_Token_Holders_test.py b/tests/batch/indexer_Token_Holders_test.py index cd24d7546..aa18b951c 100644 --- a/tests/batch/indexer_Token_Holders_test.py +++ b/tests/batch/indexer_Token_Holders_test.py @@ -40,6 +40,7 @@ bond_apply_for_transfer, bond_approve_transfer, bond_cancel_transfer, + bond_force_change_locked_account, bond_force_lock, bond_force_unlock, bond_issue_from, @@ -77,6 +78,7 @@ share_apply_for_transfer, share_approve_transfer, share_cancel_transfer, + share_force_change_locked_account, share_force_lock, share_force_unlock, share_issue_from, @@ -483,6 +485,7 @@ async def test_normal_1( # - ForceLock # - Unlock # - ForceUnlock + # - ForceChangeLockAccount async def test_normal_2( self, processor: Processor, @@ -568,17 +571,17 @@ async def test_normal_2( ) # user1: 13000 trader: 17000 - bond_lock(self.trader, token, self.issuer["account_address"], 1500) - # user1: 13000 trader: (hold: 15500, locked: 1500) + bond_lock(self.trader, token, self.issuer["account_address"], 2000) + # user1: 13000 trader: (hold: 15000, locked: 2000) bond_force_lock( self.issuer, token, self.issuer["account_address"], self.trader["account_address"], - 1500, + 2000, ) - # user1: 13000 trader: (hold: 14000, locked: 3000) + # user1: 13000 trader: (hold: 13000, locked: 4000) bond_unlock( self.issuer, @@ -587,7 +590,7 @@ async def test_normal_2( self.user1["account_address"], 1000, ) - # user1: 15000 trader: (hold: 14000, locked: 2000) + # user1: 14000 trader: (hold: 13000, locked: 3000) bond_force_unlock( self.issuer, @@ -597,7 +600,19 @@ async def test_normal_2( self.user1["account_address"], 1000, ) - # user1: 15000 trader: (hold: 14000, locked: 1000) + # user1: 15000 trader: (hold: 13000, locked: 2000) + + bond_force_change_locked_account( + self.issuer, + token, + self.issuer["account_address"], + self.trader["account_address"], + self.user1["account_address"], + 1000, + "force_unlock", + ) + # user1: (hold: 15000, locked: 1000) + # trader: (hold: 13000, locked: 1000) # Insert collection record with above token and current block number target_token_holders_list = self.token_holders_list( @@ -647,8 +662,8 @@ async def test_normal_2( ) ).first() assert user1_record.hold_balance == 15000 - assert user1_record.locked_balance == 0 - assert trader_record.hold_balance == 14000 + assert user1_record.locked_balance == 1000 + assert trader_record.hold_balance == 13000 assert trader_record.locked_balance == 1000 assert ( @@ -1020,6 +1035,7 @@ async def test_normal_4( # - ForceLock # - Unlock # - ForceUnlock + # - ForceChangeLockAccount async def test_normal_5( self, processor: Processor, @@ -1105,17 +1121,17 @@ async def test_normal_5( ) # user1: 13000 trader: 17000 - share_lock(self.trader, token, self.issuer["account_address"], 1500) - # user1: 13000 trader: (hold: 15500, locked: 1500) + share_lock(self.trader, token, self.issuer["account_address"], 2000) + # user1: 13000 trader: (hold: 15500, locked: 2000) share_force_lock( self.issuer, token, self.issuer["account_address"], self.trader["account_address"], - 1500, + 2000, ) - # user1: 13000 trader: (hold: 14000, locked: 3000) + # user1: 13000 trader: (hold: 13000, locked: 4000) share_unlock( self.issuer, @@ -1124,7 +1140,7 @@ async def test_normal_5( self.user1["account_address"], 1000, ) - # user1: 15000 trader: (hold: 14000, locked: 2000) + # user1: 14000 trader: (hold: 13000, locked: 3000) share_force_unlock( self.issuer, @@ -1134,7 +1150,19 @@ async def test_normal_5( self.user1["account_address"], 1000, ) - # user1: 15000 trader: (hold: 14000, locked: 1000) + # user1: 15000 trader: (hold: 13000, locked: 2000) + + share_force_change_locked_account( + self.issuer, + token, + self.issuer["account_address"], + self.trader["account_address"], + self.user1["account_address"], + 1000, + "force_unlock", + ) + # user1: (hold: 15000, locked: 1000) + # trader: (hold: 13000, locked: 1000) # Insert collection record with above token and current block number target_token_holders_list = self.token_holders_list( @@ -1184,8 +1212,8 @@ async def test_normal_5( ) ).first() assert user1_record.hold_balance == 15000 - assert user1_record.locked_balance == 0 - assert trader_record.hold_balance == 14000 + assert user1_record.locked_balance == 1000 + assert trader_record.hold_balance == 13000 assert trader_record.locked_balance == 1000 assert ( diff --git a/tests/batch/indexer_Transfer_test.py b/tests/batch/indexer_Transfer_test.py index 60c3f33c3..9b9c64795 100644 --- a/tests/batch/indexer_Transfer_test.py +++ b/tests/batch/indexer_Transfer_test.py @@ -52,6 +52,7 @@ membership_register_list, register_bond_list, register_share_list, + share_force_change_locked_account, share_force_unlock, share_lock, share_unlock, @@ -232,6 +233,7 @@ async def listing_token(token_address, async_session): # - Transfer # - Unlock # - ForceUnlock + # - ForceChangeLockedAccount async def test_normal_1_1(self, processor, shared_contract, async_session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -264,13 +266,13 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): share_lock( invoker=self.trader, token=share_token, - lock_address=self.trader2["account_address"], + lock_address=self.issuer["account_address"], amount=50000, data_str=json.dumps({"message": "garnishment"}), ) share_unlock( - invoker=self.trader2, + invoker=self.issuer, token=share_token, target=self.trader["account_address"], recipient=self.trader2["account_address"], @@ -284,16 +286,28 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): share_force_unlock( invoker=self.issuer, token=share_token, - lock_address=self.trader2["account_address"], + lock_address=self.issuer["account_address"], target=self.trader["account_address"], recipient=self.trader2["account_address"], - amount=20000, + amount=10000, data_str=json.dumps( {"invalid_message": "invalid_value"} ), # invalid message format ) block_number_3 = web3.eth.block_number + # emit "ForceChangeLockedAccount" + share_force_change_locked_account( + invoker=self.issuer, + token=share_token, + lock_address=self.issuer["account_address"], + before_account_address=self.trader["account_address"], + after_account_address=self.trader2["account_address"], + amount=10000, + data_str="", + ) + block_number_4 = web3.eth.block_number + # Execute batch processing await processor.sync_new_logs() @@ -303,7 +317,7 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): select(IDXTransfer).order_by(IDXTransfer.created) ) ).all() - assert len(idx_transfer_list) == 3 + assert len(idx_transfer_list) == 4 block1 = web3.eth.get_block(block_number_1) idx_transfer: IDXTransfer = idx_transfer_list[0] @@ -339,13 +353,30 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): assert idx_transfer.token_address == share_token["address"] assert idx_transfer.from_address == self.trader["account_address"] assert idx_transfer.to_address == self.trader2["account_address"] - assert idx_transfer.value == 20000 + assert idx_transfer.value == 10000 assert idx_transfer.source_event == IDXTransferSourceEventType.FORCE_UNLOCK assert idx_transfer.data == {} assert idx_transfer.message is None assert idx_transfer.created is not None assert idx_transfer.modified is not None + block4 = web3.eth.get_block(block_number_4) + idx_transfer: IDXTransfer = idx_transfer_list[3] + assert idx_transfer.id == 4 + assert idx_transfer.transaction_hash == block4["transactions"][0].to_0x_hex() + assert idx_transfer.token_address == share_token["address"] + assert idx_transfer.from_address == self.trader["account_address"] + assert idx_transfer.to_address == self.trader2["account_address"] + assert idx_transfer.value == 10000 + assert ( + idx_transfer.source_event + == IDXTransferSourceEventType.FORCE_CHANGE_LOCKED_ACCOUNT + ) + assert idx_transfer.data == {} + assert idx_transfer.message is None + assert idx_transfer.created is not None + assert idx_transfer.modified is not None + idx_block_number: IDXTransferBlockNumber = ( await async_session.scalars( select(IDXTransferBlockNumber) @@ -356,7 +387,7 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): .limit(1) ) ).first() - assert idx_block_number.latest_block_number == block_number_3 + assert idx_block_number.latest_block_number == block_number_4 # # IbetShare @@ -452,6 +483,7 @@ async def test_normal_1_2(self, processor, shared_contract, async_session): # - Transfer(twice) # - Unlock(twice) # - ForceUnlock(twice) + # - ForceChangeLockedAccount(twice) async def test_normal_2(self, processor, shared_contract, async_session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -493,16 +525,16 @@ async def test_normal_2(self, processor, shared_contract, async_session): ) block_number_2 = web3.eth.block_number - # emit "Unlock"/"ForceUnlock" + # emit "Unlock"/"ForceUnlock"/"ForceChangeLockedAccount" share_lock( invoker=self.trader, token=share_token, - lock_address=self.trader2["account_address"], + lock_address=self.issuer["account_address"], amount=100000, data_str=json.dumps({"message": "garnishment"}), ) share_unlock( - invoker=self.trader2, + invoker=self.issuer, token=share_token, target=self.trader["account_address"], recipient=self.trader2["account_address"], @@ -511,7 +543,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): ) block_number_3 = web3.eth.block_number share_unlock( - invoker=self.trader2, + invoker=self.issuer, token=share_token, target=self.trader["account_address"], recipient=self.trader2["account_address"], @@ -522,23 +554,43 @@ async def test_normal_2(self, processor, shared_contract, async_session): share_force_unlock( invoker=self.issuer, token=share_token, - lock_address=self.trader2["account_address"], + lock_address=self.issuer["account_address"], target=self.trader["account_address"], recipient=self.trader2["account_address"], - amount=20000, + amount=10000, data_str=json.dumps({"message": "force_unlock"}), ) block_number_5 = web3.eth.block_number share_force_unlock( invoker=self.issuer, token=share_token, - lock_address=self.trader2["account_address"], + lock_address=self.issuer["account_address"], target=self.trader["account_address"], recipient=self.trader2["account_address"], - amount=20000, + amount=10000, data_str=json.dumps({"message": "force_unlock"}), ) block_number_6 = web3.eth.block_number + share_force_change_locked_account( + invoker=self.issuer, + token=share_token, + lock_address=self.issuer["account_address"], + before_account_address=self.trader["account_address"], + after_account_address=self.trader2["account_address"], + amount=10000, + data_str=json.dumps({"message": "force_change"}), + ) + block_number_7 = web3.eth.block_number + share_force_change_locked_account( + invoker=self.issuer, + token=share_token, + lock_address=self.issuer["account_address"], + before_account_address=self.trader["account_address"], + after_account_address=self.trader2["account_address"], + amount=10000, + data_str="", + ) + block_number_8 = web3.eth.block_number # Execute batch processing await processor.sync_new_logs() @@ -549,7 +601,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): select(IDXTransfer).order_by(IDXTransfer.created) ) ).all() - assert len(idx_transfer_list) == 6 + assert len(idx_transfer_list) == 8 block = web3.eth.get_block(block_number_1) idx_transfer = idx_transfer_list[0] @@ -612,7 +664,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): assert idx_transfer.token_address == share_token["address"] assert idx_transfer.from_address == self.trader["account_address"] assert idx_transfer.to_address == self.trader2["account_address"] - assert idx_transfer.value == 20000 + assert idx_transfer.value == 10000 assert idx_transfer.source_event == IDXTransferSourceEventType.FORCE_UNLOCK assert idx_transfer.data == {"message": "force_unlock"} assert idx_transfer.message == "force_unlock" @@ -626,13 +678,47 @@ async def test_normal_2(self, processor, shared_contract, async_session): assert idx_transfer.token_address == share_token["address"] assert idx_transfer.from_address == self.trader["account_address"] assert idx_transfer.to_address == self.trader2["account_address"] - assert idx_transfer.value == 20000 + assert idx_transfer.value == 10000 assert idx_transfer.source_event == IDXTransferSourceEventType.FORCE_UNLOCK assert idx_transfer.data == {"message": "force_unlock"} assert idx_transfer.message == "force_unlock" assert idx_transfer.created is not None assert idx_transfer.modified is not None + block = web3.eth.get_block(block_number_7) + idx_transfer = idx_transfer_list[6] + assert idx_transfer.id == 7 + assert idx_transfer.transaction_hash == block["transactions"][0].to_0x_hex() + assert idx_transfer.token_address == share_token["address"] + assert idx_transfer.from_address == self.trader["account_address"] + assert idx_transfer.to_address == self.trader2["account_address"] + assert idx_transfer.value == 10000 + assert ( + idx_transfer.source_event + == IDXTransferSourceEventType.FORCE_CHANGE_LOCKED_ACCOUNT + ) + assert idx_transfer.data == {} + assert idx_transfer.message is None + assert idx_transfer.created is not None + assert idx_transfer.modified is not None + + block = web3.eth.get_block(block_number_8) + idx_transfer = idx_transfer_list[7] + assert idx_transfer.id == 8 + assert idx_transfer.transaction_hash == block["transactions"][0].to_0x_hex() + assert idx_transfer.token_address == share_token["address"] + assert idx_transfer.from_address == self.trader["account_address"] + assert idx_transfer.to_address == self.trader2["account_address"] + assert idx_transfer.value == 10000 + assert ( + idx_transfer.source_event + == IDXTransferSourceEventType.FORCE_CHANGE_LOCKED_ACCOUNT + ) + assert idx_transfer.data == {} + assert idx_transfer.message is None + assert idx_transfer.created is not None + assert idx_transfer.modified is not None + idx_block_number: IDXTransferBlockNumber = ( await async_session.scalars( select(IDXTransferBlockNumber) @@ -642,7 +728,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): .limit(1) ) ).first() - assert idx_block_number.latest_block_number == block_number_6 + assert idx_block_number.latest_block_number == block_number_8 # # IbetStraightBond, IbetMembership, IbetCoupon @@ -870,7 +956,7 @@ async def test_normal_4_1(self, processor, shared_contract, async_session, caplo ).first() assert idx_block_number.latest_block_number == block_number_1 - assert 3 == caplog.record_tuples.count( + assert 4 == caplog.record_tuples.count( ( LOG.name, logging.DEBUG, @@ -976,7 +1062,7 @@ async def test_normal_4_2(self, processor, shared_contract, async_session, caplo ).first() assert idx_block_number.latest_block_number == block_number_2 - assert 3 == caplog.record_tuples.count( + assert 4 == caplog.record_tuples.count( ( LOG.name, logging.DEBUG, @@ -1089,7 +1175,7 @@ async def test_normal_4_3(self, processor, shared_contract, async_session, caplo ).first() assert idx_block_number.latest_block_number == block_number_2 - assert 3 == caplog.record_tuples.count( + assert 4 == caplog.record_tuples.count( ( LOG.name, logging.DEBUG, @@ -1220,7 +1306,7 @@ async def test_normal_4_5(self, processor, shared_contract, async_session, caplo ).first() assert idx_block_number.latest_block_number == block_number - assert 3 == caplog.record_tuples.count( + assert 4 == caplog.record_tuples.count( ( LOG.name, logging.DEBUG, diff --git a/tests/contract_modules.py b/tests/contract_modules.py index 1c3a99cbf..854df117e 100644 --- a/tests/contract_modules.py +++ b/tests/contract_modules.py @@ -329,6 +329,23 @@ def bond_force_unlock( ).transact({"from": invoker["account_address"]}) +# BONDトークン:資産ロック元強制変更 +def bond_force_change_locked_account( + invoker, + token, + lock_address: str, + before_account_address: str, + after_account_address: str, + amount: int, + data_str: str = "", +): + web3.eth.default_account = invoker["account_address"] + TokenContract = Contract.get_contract("IbetStraightBond", token["address"]) + TokenContract.functions.forceChangeLockedAccount( + lock_address, before_account_address, after_account_address, amount, data_str + ).transact({"from": invoker["account_address"]}) + + # BONDトークン:追加発行 def bond_issue_from( invoker, token, target: str, amount: int, lock_address: str = config.ZERO_ADDRESS @@ -570,6 +587,23 @@ def share_force_unlock( ).transact({"from": invoker["account_address"]}) +# SHAREトークン:資産ロック元強制変更 +def share_force_change_locked_account( + invoker, + token, + lock_address: str, + before_account_address: str, + after_account_address: str, + amount: int, + data_str: str = "", +): + web3.eth.default_account = invoker["account_address"] + TokenContract = Contract.get_contract("IbetShare", token["address"]) + TokenContract.functions.forceChangeLockedAccount( + lock_address, before_account_address, after_account_address, amount, data_str + ).transact({"from": invoker["account_address"]}) + + # SHAREトークン:追加発行 def share_issue_from( invoker, token, target: str, amount: int, lock_address: str = config.ZERO_ADDRESS From 8a75b75add05b3bd4efb943bca4213ee69ca4d78 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Fri, 27 Jun 2025 09:20:38 +0900 Subject: [PATCH 02/22] Replace aiohttp with requests in batch processing modules (25.9) (#1654) * Replace aiohttp with requests in batch processing modules * Update deps * Update deps for explorer --- batch/indexer_Company_List.py | 65 ++++++++------- batch/indexer_PublicInfo_PublicAccountList.py | 53 ++++++------ batch/indexer_PublicInfo_TokenList.py | 61 +++++++------- batch/processor_Send_Chat_Webhook.py | 33 ++++---- cmd/explorer/pyproject.toml | 4 +- cmd/explorer/uv.lock | 3 +- pyproject.toml | 4 +- tests/batch/indexer_Company_List_test.py | 82 +++++++++---------- ...dexer_PublicInfo_PublicAccountList_test.py | 50 +++++------ .../indexer_PublicInfo_TokenList_test.py | 54 ++++++------ .../batch/processor_Send_Chat_Webhook_test.py | 26 ++---- uv.lock | 44 +++++----- 12 files changed, 228 insertions(+), 251 deletions(-) diff --git a/batch/indexer_Company_List.py b/batch/indexer_Company_List.py index f31f2eff8..7242240e5 100644 --- a/batch/indexer_Company_List.py +++ b/batch/indexer_Company_List.py @@ -17,21 +17,24 @@ SPDX-License-Identifier: Apache-2.0 """ -import asyncio import hashlib import json import sys import time -import aiohttp -from aiohttp import ClientTimeout +import requests from eth_utils import to_checksum_address from sqlalchemy import delete +from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.ext.asyncio import AsyncSession - -from app.config import COMPANY_LIST_SLEEP_INTERVAL, COMPANY_LIST_URL, REQUEST_TIMEOUT -from app.database import BatchAsyncSessionLocal +from sqlalchemy.orm.session import Session + +from app.config import ( + COMPANY_LIST_SLEEP_INTERVAL, + COMPANY_LIST_URL, + DATABASE_URL, + REQUEST_TIMEOUT, +) from app.errors import ServiceUnavailable from app.model.db import Company from batch import free_malloc, log @@ -39,6 +42,8 @@ process_name = "INDEXER-COMPANY-LIST" LOG = log.get_logger(process_name=process_name) +db_engine = create_engine(DATABASE_URL, echo=False, pool_pre_ping=True) + class Processor: """Processor for indexing company list""" @@ -46,21 +51,15 @@ class Processor: def __init__(self): self.company_list_digest = None - async def process(self): + def process(self): LOG.info("Syncing company list") # Get from COMPANY_LIST_URL try: - async with aiohttp.ClientSession(trust_env=True) as session: - async with session.get( - COMPANY_LIST_URL, - timeout=ClientTimeout( - connect=REQUEST_TIMEOUT[0], total=REQUEST_TIMEOUT[1] - ), - ) as response: - if response.status != 200: - raise Exception(f"status code={response.status}") - company_list_json = await response.json() + _resp = requests.get(COMPANY_LIST_URL, timeout=REQUEST_TIMEOUT) + if _resp.status_code != 200: + raise Exception(f"status code={_resp.status_code}") + company_list_json = _resp.json() except Exception: LOG.exception("Failed to get company list") return @@ -76,10 +75,10 @@ async def process(self): self.company_list_digest = _resp_digest # Update DB data - db_session = BatchAsyncSessionLocal() + db_session = Session(autocommit=False, autoflush=True, bind=db_engine) try: # Delete all company list from DB - await db_session.execute(delete(Company)) + db_session.execute(delete(Company)) # Insert company list for i, company in enumerate(company_list_json): @@ -105,7 +104,7 @@ async def process(self): except ValueError: LOG.notice(f"Invalid address: index={i} address={address}") continue - await self.__sink_on_company( + self.__sink_on_company( db_session=db_session, address=to_checksum_address(address), corporate_name=corporate_name, @@ -116,17 +115,17 @@ async def process(self): LOG.notice(f"Missing required field: index={i}") continue - await db_session.commit() - except Exception as e: - await db_session.rollback() - raise e + db_session.commit() + except Exception: + db_session.rollback() + raise finally: - await db_session.close() + db_session.close() LOG.info("Sync job has been completed") @staticmethod - async def __sink_on_company( - db_session: AsyncSession, + def __sink_on_company( + db_session: Session, address: str, corporate_name: str, rsa_publickey: str, @@ -137,17 +136,17 @@ async def __sink_on_company( _company.corporate_name = corporate_name _company.rsa_publickey = rsa_publickey _company.homepage = homepage - await db_session.merge(_company) + db_session.merge(_company) -async def main(): +def main(): LOG.info("Service started successfully") processor = Processor() while True: start_time = time.time() try: - await processor.process() + processor.process() except ServiceUnavailable: LOG.notice("An external service was unavailable") except SQLAlchemyError as sa_err: @@ -156,12 +155,12 @@ async def main(): LOG.exception("An exception occurred during processing") elapsed_time = time.time() - start_time - await asyncio.sleep(max(COMPANY_LIST_SLEEP_INTERVAL - elapsed_time, 0)) + time.sleep(max(COMPANY_LIST_SLEEP_INTERVAL - elapsed_time, 0)) free_malloc() if __name__ == "__main__": try: - asyncio.run(main()) + main() except KeyboardInterrupt: sys.exit(1) diff --git a/batch/indexer_PublicInfo_PublicAccountList.py b/batch/indexer_PublicInfo_PublicAccountList.py index 4a96dfef9..35b9148e1 100644 --- a/batch/indexer_PublicInfo_PublicAccountList.py +++ b/batch/indexer_PublicInfo_PublicAccountList.py @@ -17,25 +17,24 @@ SPDX-License-Identifier: Apache-2.0 """ -import asyncio import hashlib import json import sys import time -import aiohttp -from aiohttp import ClientTimeout +import requests from eth_utils import to_checksum_address from sqlalchemy import delete +from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm.session import Session from app.config import ( + DATABASE_URL, PUBLIC_ACCOUNT_LIST_SLEEP_INTERVAL, PUBLIC_ACCOUNT_LIST_URL, REQUEST_TIMEOUT, ) -from app.database import BatchAsyncSessionLocal from app.errors import ServiceUnavailable from app.model.db import PublicAccountList from batch import free_malloc, log @@ -43,6 +42,8 @@ process_name = "INDEXER-PUBLIC-INFO-PUBLIC-ACCOUNT" LOG = log.get_logger(process_name=process_name) +db_engine = create_engine(DATABASE_URL, echo=False, pool_pre_ping=True) + class Processor: """Processor for indexing public account list""" @@ -50,21 +51,15 @@ class Processor: def __init__(self): self.account_list_digest = None - async def process(self): + def process(self): LOG.info("Syncing public account list") # Get data from PUBLIC_ACCOUNT_LIST_URL try: - async with aiohttp.ClientSession(trust_env=True) as session: - async with session.get( - PUBLIC_ACCOUNT_LIST_URL, - timeout=ClientTimeout( - connect=REQUEST_TIMEOUT[0], total=REQUEST_TIMEOUT[1] - ), - ) as response: - if response.status != 200: - raise Exception(f"status code={response.status}") - account_list_json = await response.json() + _resp = requests.get(PUBLIC_ACCOUNT_LIST_URL, timeout=REQUEST_TIMEOUT) + if _resp.status_code != 200: + raise Exception(f"status code={_resp.status_code}") + account_list_json = _resp.json() except Exception: LOG.exception("Failed to get public account list") return @@ -80,10 +75,10 @@ async def process(self): self.account_list_digest = _resp_digest # Update DB data - db_session = BatchAsyncSessionLocal() + db_session = Session(autocommit=False, autoflush=True, bind=db_engine) try: # Delete all account list from DB - await db_session.execute(delete(PublicAccountList)) + db_session.execute(delete(PublicAccountList)) # Insert account list for i, _account in enumerate(account_list_json): @@ -109,7 +104,7 @@ async def process(self): f"Invalid address: index={i} account_address={account_address}" ) continue - await self.__sink_on_account_list( + self.__sink_on_account_list( db_session=db_session, key_manager=key_manager, key_manager_name=key_manager_name, @@ -117,18 +112,18 @@ async def process(self): account_address=account_address, ) - await db_session.commit() + db_session.commit() except Exception as e: - await db_session.rollback() + db_session.rollback() raise e finally: - await db_session.close() + db_session.close() LOG.info("Sync job has been completed") @staticmethod - async def __sink_on_account_list( - db_session: AsyncSession, + def __sink_on_account_list( + db_session: Session, key_manager: str, key_manager_name: str, account_type: int, @@ -139,17 +134,17 @@ async def __sink_on_account_list( _account_list.key_manager_name = key_manager_name _account_list.account_type = account_type _account_list.account_address = account_address - await db_session.merge(_account_list) + db_session.merge(_account_list) -async def main(): +def main(): LOG.info("Service started successfully") processor = Processor() while True: start_time = time.time() try: - await processor.process() + processor.process() except ServiceUnavailable: LOG.notice("An external service was unavailable") except SQLAlchemyError as sa_err: @@ -158,12 +153,12 @@ async def main(): LOG.exception("An exception occurred during processing") elapsed_time = time.time() - start_time - await asyncio.sleep(max(PUBLIC_ACCOUNT_LIST_SLEEP_INTERVAL - elapsed_time, 0)) + time.sleep(max(PUBLIC_ACCOUNT_LIST_SLEEP_INTERVAL - elapsed_time, 0)) free_malloc() if __name__ == "__main__": try: - asyncio.run(main()) + main() except KeyboardInterrupt: sys.exit(1) diff --git a/batch/indexer_PublicInfo_TokenList.py b/batch/indexer_PublicInfo_TokenList.py index b15b74f11..90051c7dc 100644 --- a/batch/indexer_PublicInfo_TokenList.py +++ b/batch/indexer_PublicInfo_TokenList.py @@ -17,21 +17,24 @@ SPDX-License-Identifier: Apache-2.0 """ -import asyncio import hashlib import json import sys import time -import aiohttp -from aiohttp import ClientTimeout +import requests from eth_utils import to_checksum_address from sqlalchemy import delete +from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.ext.asyncio import AsyncSession - -from app.config import REQUEST_TIMEOUT, TOKEN_LIST_SLEEP_INTERVAL, TOKEN_LIST_URL -from app.database import BatchAsyncSessionLocal +from sqlalchemy.orm.session import Session + +from app.config import ( + DATABASE_URL, + REQUEST_TIMEOUT, + TOKEN_LIST_SLEEP_INTERVAL, + TOKEN_LIST_URL, +) from app.errors import ServiceUnavailable from app.model.db import TokenList from batch import free_malloc, log @@ -39,6 +42,8 @@ process_name = "INDEXER-PUBLIC-INFO-TOKEN-LIST" LOG = log.get_logger(process_name=process_name) +db_engine = create_engine(DATABASE_URL, echo=False, pool_pre_ping=True) + class Processor: """Processor for indexing token list""" @@ -46,21 +51,15 @@ class Processor: def __init__(self): self.token_list_digest = None - async def process(self): + def process(self): LOG.info("Syncing token list") # Get from TOKEN_LIST_URL try: - async with aiohttp.ClientSession(trust_env=True) as session: - async with session.get( - TOKEN_LIST_URL, - timeout=ClientTimeout( - connect=REQUEST_TIMEOUT[0], total=REQUEST_TIMEOUT[1] - ), - ) as response: - if response.status != 200: - raise Exception(f"status code={response.status}") - token_list_json = await response.json() + _resp = requests.get(TOKEN_LIST_URL, timeout=REQUEST_TIMEOUT) + if _resp.status_code != 200: + raise Exception(f"status code={_resp.status_code}") + token_list_json = _resp.json() except Exception: LOG.exception("Failed to get token list") return @@ -74,10 +73,10 @@ async def process(self): self.token_list_digest = _resp_digest # Update DB data - db_session = BatchAsyncSessionLocal() + db_session = Session(autocommit=False, autoflush=True, bind=db_engine) try: # Delete all token list from DB - await db_session.execute(delete(TokenList)) + db_session.execute(delete(TokenList)) # Insert token list for i, token in enumerate(token_list_json): @@ -104,7 +103,7 @@ async def process(self): ) continue - await self.__sink_on_token_list( + self.__sink_on_token_list( db_session=db_session, token_address=token_address, token_template=token_template, @@ -112,18 +111,18 @@ async def process(self): product_type=product_type, ) - await db_session.commit() + db_session.commit() except Exception as e: - await db_session.rollback() + db_session.rollback() raise e finally: - await db_session.close() + db_session.close() LOG.info("Sync job has been completed") @staticmethod - async def __sink_on_token_list( - db_session: AsyncSession, + def __sink_on_token_list( + db_session: Session, token_address: str, token_template: str, key_manager: list[str], @@ -134,17 +133,17 @@ async def __sink_on_token_list( _token_list.token_template = token_template _token_list.key_manager = key_manager _token_list.product_type = product_type - await db_session.merge(_token_list) + db_session.merge(_token_list) -async def main(): +def main(): LOG.info("Service started successfully") processor = Processor() while True: start_time = time.time() try: - await processor.process() + processor.process() except ServiceUnavailable: LOG.notice("An external service was unavailable") except SQLAlchemyError as sa_err: @@ -153,12 +152,12 @@ async def main(): LOG.exception("An exception occurred during processing") elapsed_time = time.time() - start_time - await asyncio.sleep(max(TOKEN_LIST_SLEEP_INTERVAL - elapsed_time, 0)) + time.sleep(max(TOKEN_LIST_SLEEP_INTERVAL - elapsed_time, 0)) free_malloc() if __name__ == "__main__": try: - asyncio.run(main()) + main() except KeyboardInterrupt: sys.exit(1) diff --git a/batch/processor_Send_Chat_Webhook.py b/batch/processor_Send_Chat_Webhook.py index 0e272fcf2..77b6f7908 100644 --- a/batch/processor_Send_Chat_Webhook.py +++ b/batch/processor_Send_Chat_Webhook.py @@ -17,67 +17,68 @@ SPDX-License-Identifier: Apache-2.0 """ -import asyncio import sys import time from typing import Sequence -import aiohttp +import requests from sqlalchemy import select +from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm.session import Session -from app.config import CHAT_WEBHOOK_URL -from app.database import BatchAsyncSessionLocal +from app.config import CHAT_WEBHOOK_URL, DATABASE_URL from app.model.db import ChatWebhook from batch import free_malloc, log LOG = log.get_logger(process_name="PROCESSOR-SEND-CHAT-WEBHOOK") +db_engine = create_engine(DATABASE_URL, echo=False, pool_pre_ping=True) + class Processor: - async def process(self): - db_session = BatchAsyncSessionLocal() + def process(self): + db_session = Session(autocommit=False, autoflush=True, bind=db_engine) try: hook_list: Sequence[ChatWebhook] = ( - await db_session.scalars(select(ChatWebhook)) + db_session.scalars(select(ChatWebhook)) ).all() if len(hook_list) > 0: LOG.info("Process start") for hook in hook_list: try: - async with aiohttp.ClientSession(trust_env=True) as session: - await session.post(CHAT_WEBHOOK_URL, json=hook.message) + requests.post(CHAT_WEBHOOK_URL, json=hook.message) except Exception: LOG.exception("Failed to send chat webhook") finally: # Delete the message regardless of the status code of the response. - await db_session.delete(hook) - await db_session.commit() + db_session.delete(hook) + db_session.commit() LOG.info("Process end") finally: - await db_session.close() + db_session.close() -async def main(): +def main(): LOG.info("Service started successfully") processor = Processor() while True: start_time = time.time() try: - await processor.process() + processor.process() except SQLAlchemyError as sa_err: LOG.error(f"A database error has occurred: code={sa_err.code}\n{sa_err}") except Exception as ex: LOG.exception(ex) elapsed_time = time.time() - start_time - await asyncio.sleep(max(30 - elapsed_time, 0)) + time.sleep(max(30 - elapsed_time, 0)) free_malloc() if __name__ == "__main__": try: - asyncio.run(main()) + main() except KeyboardInterrupt: sys.exit(1) diff --git a/cmd/explorer/pyproject.toml b/cmd/explorer/pyproject.toml index def82606f..321100ce5 100644 --- a/cmd/explorer/pyproject.toml +++ b/cmd/explorer/pyproject.toml @@ -6,7 +6,7 @@ authors = [ {name = "BOOSTRY Co., Ltd.", email = "dev@boostry.co.jp"}, ] readme = "README.md" -requires-python = "==3.12.2" +requires-python = "==3.12.9" dependencies = [] [project.scripts] @@ -20,4 +20,4 @@ check_untyped_defs = true [tool.setuptools.packages.find] where = ["src/"] -include = ["*"] \ No newline at end of file +include = ["*"] diff --git a/cmd/explorer/uv.lock b/cmd/explorer/uv.lock index b24abd34c..232581cce 100644 --- a/cmd/explorer/uv.lock +++ b/cmd/explorer/uv.lock @@ -1,5 +1,6 @@ version = 1 -requires-python = "==3.12.2" +revision = 2 +requires-python = "==3.12.9" [[package]] name = "ibet-wallet-api-explorer" diff --git a/pyproject.toml b/pyproject.toml index 09aa6c6d8..948909290 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ dependencies = [ "alembic<2.0.0,>=1.14.0", "aiomysql==0.2.0", "httpx~=0.28.0", - "aiohttp~=3.10.10", "memray<2.0.0,>=1.14.0", "py-spy>=0.4.0", "asyncpg~=0.30.0", @@ -41,6 +40,7 @@ dependencies = [ "opentelemetry-instrumentation-psycopg>=0.54b0,<1.0.0", "opentelemetry-instrumentation-sqlalchemy>=0.54b0,<1.0.0", "opentelemetry-exporter-otlp-proto-grpc>=1.33.1,<2.0.0", + "requests>=2.32.3", ] [tool.uv] @@ -66,6 +66,8 @@ ibet-explorer = [ "textual~=0.44.1", "async-cache~=1.1.1", "typer~=0.12.3", + "aiohttp~=3.12.13", + "async-cache==1.1.1", ] [tool.uv.sources] diff --git a/tests/batch/indexer_Company_List_test.py b/tests/batch/indexer_Company_List_test.py index e8fe8e61b..1b5479546 100644 --- a/tests/batch/indexer_Company_List_test.py +++ b/tests/batch/indexer_Company_List_test.py @@ -50,15 +50,9 @@ def caplog(caplog: pytest.LogCaptureFixture): class MockResponse: def __init__(self, data: object, status_code: int = 200): self.data = data - self.status = status_code + self.status_code = status_code - async def __aexit__(self, exc_type, exc, tb): - pass - - async def __aenter__(self): - return self - - async def json(self) -> object: + def json(self) -> object: return self.data @@ -70,7 +64,7 @@ class TestProcessor: # # 0 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_1(self, mock_get, processor, session): # Prepare data _company = Company() @@ -97,7 +91,7 @@ async def test_normal_1(self, mock_get, processor, session): mock_get.side_effect = [MockResponse([])] # Run target process - await processor.process() + processor.process() # Assertion session.rollback() @@ -108,7 +102,7 @@ async def test_normal_1(self, mock_get, processor, session): # # 1 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -146,7 +140,7 @@ async def test_normal_2(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -163,7 +157,7 @@ async def test_normal_2(self, mock_get, processor, session): # # 2 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -206,7 +200,7 @@ async def test_normal_3(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -232,7 +226,7 @@ async def test_normal_3(self, mock_get, processor, session): # Insert SKIP # type error # address - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_1_1(self, mock_get, processor, session): # Prepare data _company = Company() @@ -276,7 +270,7 @@ async def test_normal_4_1_1(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -295,7 +289,7 @@ async def test_normal_4_1_1(self, mock_get, processor, session): # Insert SKIP # type error # corporate_name - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_1_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -339,7 +333,7 @@ async def test_normal_4_1_2(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -358,7 +352,7 @@ async def test_normal_4_1_2(self, mock_get, processor, session): # Insert SKIP # type error # rsa_publickey - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_1_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -402,7 +396,7 @@ async def test_normal_4_1_3(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -421,7 +415,7 @@ async def test_normal_4_1_3(self, mock_get, processor, session): # Insert SKIP # type error # homepage - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_1_4(self, mock_get, processor, session): # Prepare data _company = Company() @@ -465,7 +459,7 @@ async def test_normal_4_1_4(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -484,7 +478,7 @@ async def test_normal_4_1_4(self, mock_get, processor, session): # Insert SKIP # required error # address - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_2_1(self, mock_get, processor, session): # Prepare data _company = Company() @@ -527,7 +521,7 @@ async def test_normal_4_2_1(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -546,7 +540,7 @@ async def test_normal_4_2_1(self, mock_get, processor, session): # Insert SKIP # required error # corporate_name - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_2_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -589,7 +583,7 @@ async def test_normal_4_2_2(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -608,7 +602,7 @@ async def test_normal_4_2_2(self, mock_get, processor, session): # Insert SKIP # required error # address - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_2_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -651,7 +645,7 @@ async def test_normal_4_2_3(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -669,7 +663,7 @@ async def test_normal_4_2_3(self, mock_get, processor, session): # # Insert SKIP # invalid address error - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -713,7 +707,7 @@ async def test_normal_4_3(self, mock_get, processor, session): ] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -731,7 +725,7 @@ async def test_normal_4_3(self, mock_get, processor, session): # # There are no differences from last time # -> Skip this cycle - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_5_1(self, mock_get, processor, session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -752,7 +746,7 @@ async def test_normal_5_1(self, mock_get, processor, session, caplog): ] ) ] - await processor.process() + processor.process() # Run target process: 2nd time mock_get.side_effect = [ @@ -773,7 +767,7 @@ async def test_normal_5_1(self, mock_get, processor, session, caplog): ] ) ] - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -807,7 +801,7 @@ async def test_normal_5_1(self, mock_get, processor, session, caplog): # # There are differences from the previous cycle - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_5_2(self, mock_get, processor, session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -828,7 +822,7 @@ async def test_normal_5_2(self, mock_get, processor, session, caplog): ] ) ] - await processor.process() + processor.process() # Run target process: 2nd time mock_get.side_effect = [ @@ -843,7 +837,7 @@ async def test_normal_5_2(self, mock_get, processor, session, caplog): ] ) ] - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -875,7 +869,7 @@ async def test_normal_5_2(self, mock_get, processor, session, caplog): # API error # Connection error @mock.patch( - "aiohttp.client.ClientSession.get", + "requests.get", MagicMock(side_effect=requests.exceptions.ConnectionError), ) async def test_error_1_1(self, processor, session): @@ -901,7 +895,7 @@ async def test_error_1_1(self, processor, session): session.commit() # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -912,7 +906,7 @@ async def test_error_1_1(self, processor, session): # # API error # not succeed api - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_error_1_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -939,7 +933,7 @@ async def test_error_1_2(self, mock_get, processor, session): mock_get.side_effect = [MockResponse([], 400)] # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -950,7 +944,7 @@ async def test_error_1_2(self, mock_get, processor, session): # # not decode response @mock.patch( - "aiohttp.client.ClientSession.get", + "requests.get", MagicMock(side_effect=json.decoder.JSONDecodeError), ) async def test_error_2(self, processor, session): @@ -976,7 +970,7 @@ async def test_error_2(self, processor, session): session.commit() # Run target process - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( @@ -986,7 +980,7 @@ async def test_error_2(self, processor, session): # # other error - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_error_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -1026,7 +1020,7 @@ async def test_error_3(self, mock_get, processor, session): # Run target process with pytest.raises(Exception): - await processor.process() + processor.process() # Assertion _company_list: Sequence[Company] = session.scalars( diff --git a/tests/batch/indexer_PublicInfo_PublicAccountList_test.py b/tests/batch/indexer_PublicInfo_PublicAccountList_test.py index 5efd8cb9b..ef4c9f6fe 100644 --- a/tests/batch/indexer_PublicInfo_PublicAccountList_test.py +++ b/tests/batch/indexer_PublicInfo_PublicAccountList_test.py @@ -50,15 +50,9 @@ def caplog(caplog: pytest.LogCaptureFixture): class MockResponse: def __init__(self, data: object, status_code: int = 200): self.data = data - self.status = status_code + self.status_code = status_code - async def __aexit__(self, exc_type, exc, tb): - pass - - async def __aenter__(self): - return self - - async def json(self) -> object: + def json(self) -> object: return self.data @@ -78,7 +72,7 @@ class TestProcessor: # # 0 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_1(self, mock_get, processor, async_session): # Prepare data _account_list = PublicAccountList() @@ -93,7 +87,7 @@ async def test_normal_1(self, mock_get, processor, async_session): mock_get.side_effect = [MockResponse([])] # Run target process - await processor.process() + processor.process() # Assertion # - The cleanup process should remove all data. @@ -105,7 +99,7 @@ async def test_normal_1(self, mock_get, processor, async_session): # # Multiple records - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_2(self, mock_get, processor, async_session): # Prepare data _account_list = PublicAccountList() @@ -137,7 +131,7 @@ async def test_normal_2(self, mock_get, processor, async_session): ] # Run target process - await processor.process() + processor.process() # Assertion # - The cleanup process should remove all data. @@ -169,7 +163,7 @@ async def test_normal_2(self, mock_get, processor, async_session): # # There are no differences from last time # -> Skip this cycle - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_3_1(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -190,7 +184,7 @@ async def test_normal_3_1(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Run target process: 2nd time mock_get.side_effect = [ @@ -211,7 +205,7 @@ async def test_normal_3_1(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Assertion # - The cleanup process should remove all of your data. @@ -250,7 +244,7 @@ async def test_normal_3_1(self, mock_get, processor, async_session, caplog): # # There are differences from last time - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_3_2(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -271,7 +265,7 @@ async def test_normal_3_2(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Run target process: 2nd time mock_get.side_effect = [ @@ -286,7 +280,7 @@ async def test_normal_3_2(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Assertion # - The cleanup process should remove all data. @@ -323,7 +317,7 @@ async def test_normal_3_2(self, mock_get, processor, async_session, caplog): # # API error: ConnectionError @mock.patch( - "aiohttp.client.ClientSession.get", + "requests.get", MagicMock(side_effect=requests.exceptions.ConnectionError), ) async def test_error_1_1(self, processor, async_session): @@ -337,7 +331,7 @@ async def test_error_1_1(self, processor, async_session): await async_session.commit() # Run target process - await processor.process() + processor.process() # Assertion # - The cleanup process should be rolled back and @@ -362,7 +356,7 @@ async def test_error_1_1(self, processor, async_session): # # API error: Not succeed request - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_error_1_2(self, mock_get, processor, async_session): # Prepare data _account_list = PublicAccountList() @@ -377,7 +371,7 @@ async def test_error_1_2(self, mock_get, processor, async_session): mock_get.side_effect = [MockResponse([], 400)] # Run target process - await processor.process() + processor.process() # Assertion # - The cleanup process should be rolled back and @@ -403,7 +397,7 @@ async def test_error_1_2(self, mock_get, processor, async_session): # # API error: JSONDecodeError @mock.patch( - "aiohttp.client.ClientSession.get", + "requests.get", MagicMock(side_effect=json.decoder.JSONDecodeError), ) async def test_error_1_3(self, processor, async_session): @@ -417,7 +411,7 @@ async def test_error_1_3(self, processor, async_session): await async_session.commit() # Run target process - await processor.process() + processor.process() # Assertion # - The cleanup process should be rolled back and @@ -443,7 +437,7 @@ async def test_error_1_3(self, processor, async_session): # # Invalid type error # -> Skip processing - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") @pytest.mark.parametrize( "invalid_record", [ @@ -513,7 +507,7 @@ async def test_error_2(self, mock_get, processor, async_session, invalid_record) mock_get.side_effect = [MockResponse([invalid_record])] # Run target process - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -528,7 +522,7 @@ async def test_error_2(self, mock_get, processor, async_session, invalid_record) # # Other error - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_error_3(self, mock_get, processor, async_session): # Mock mock_get.side_effect = [ @@ -547,7 +541,7 @@ async def test_error_3(self, mock_get, processor, async_session): # Run target process with pytest.raises(Exception): - await processor.process() + processor.process() # Assertion await async_session.rollback() diff --git a/tests/batch/indexer_PublicInfo_TokenList_test.py b/tests/batch/indexer_PublicInfo_TokenList_test.py index 2e364472c..93fedb88a 100644 --- a/tests/batch/indexer_PublicInfo_TokenList_test.py +++ b/tests/batch/indexer_PublicInfo_TokenList_test.py @@ -50,15 +50,9 @@ def caplog(caplog: pytest.LogCaptureFixture): class MockResponse: def __init__(self, data: object, status_code: int = 200): self.data = data - self.status = status_code + self.status_code = status_code - async def __aexit__(self, exc_type, exc, tb): - pass - - async def __aenter__(self): - return self - - async def json(self) -> object: + def json(self) -> object: return self.data @@ -76,7 +70,7 @@ class TestProcessor: # # 0 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_1(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -103,7 +97,7 @@ async def test_normal_1(self, mock_get, processor, async_session): mock_get.side_effect = [MockResponse([])] # Run target process - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -116,7 +110,7 @@ async def test_normal_1(self, mock_get, processor, async_session): # # 1 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_2(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -154,7 +148,7 @@ async def test_normal_2(self, mock_get, processor, async_session): ] # Run target process - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -171,7 +165,7 @@ async def test_normal_2(self, mock_get, processor, async_session): # # 2 record - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_3(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -215,7 +209,7 @@ async def test_normal_3(self, mock_get, processor, async_session): ] # Run target process - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -237,7 +231,7 @@ async def test_normal_3(self, mock_get, processor, async_session): # # There are no differences from last time # -> Skip this cycle - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_1(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -258,7 +252,7 @@ async def test_normal_4_1(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Run target process: 2nd time mock_get.side_effect = [ @@ -279,7 +273,7 @@ async def test_normal_4_1(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -308,7 +302,7 @@ async def test_normal_4_1(self, mock_get, processor, async_session, caplog): # # There are differences from the previous cycle - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_normal_4_2(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -329,7 +323,7 @@ async def test_normal_4_2(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Run target process: 2nd time mock_get.side_effect = [ @@ -344,7 +338,7 @@ async def test_normal_4_2(self, mock_get, processor, async_session, caplog): ] ) ] - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -374,7 +368,7 @@ async def test_normal_4_2(self, mock_get, processor, async_session, caplog): # # API error: ConnectionError @mock.patch( - "aiohttp.client.ClientSession.get", + "requests.get", MagicMock(side_effect=requests.exceptions.ConnectionError), ) async def test_error_1_1(self, processor, async_session): @@ -388,7 +382,7 @@ async def test_error_1_1(self, processor, async_session): await async_session.commit() # Run target process - await processor.process() + processor.process() # Assertion _token_list: Sequence[TokenList] = ( @@ -400,7 +394,7 @@ async def test_error_1_1(self, processor, async_session): # # API error: Not succeed request - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_error_1_2(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -415,7 +409,7 @@ async def test_error_1_2(self, mock_get, processor, async_session): mock_get.side_effect = [MockResponse([], 400)] # Run target process - await processor.process() + processor.process() # Assertion _token_list: Sequence[TokenList] = ( @@ -428,7 +422,7 @@ async def test_error_1_2(self, mock_get, processor, async_session): # # API error: JSONDecodeError @mock.patch( - "aiohttp.client.ClientSession.get", + "requests.get", MagicMock(side_effect=json.decoder.JSONDecodeError), ) async def test_error_1_3(self, processor, async_session): @@ -442,7 +436,7 @@ async def test_error_1_3(self, processor, async_session): await async_session.commit() # Run target process - await processor.process() + processor.process() # Assertion _token_list: Sequence[TokenList] = ( @@ -455,7 +449,7 @@ async def test_error_1_3(self, processor, async_session): # # Invalid type error # -> Skip processing - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") @pytest.mark.parametrize( "invalid_record", [ @@ -543,7 +537,7 @@ async def test_error_2(self, mock_get, processor, async_session, invalid_record) ] # Run target process - await processor.process() + processor.process() # Assertion await async_session.rollback() @@ -560,7 +554,7 @@ async def test_error_2(self, mock_get, processor, async_session, invalid_record) # # Other error - @mock.patch("aiohttp.client.ClientSession.get") + @mock.patch("requests.get") async def test_error_3(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -600,7 +594,7 @@ async def test_error_3(self, mock_get, processor, async_session): # Run target process with pytest.raises(Exception): - await processor.process() + processor.process() # Assertion _token_list: Sequence[TokenList] = ( diff --git a/tests/batch/processor_Send_Chat_Webhook_test.py b/tests/batch/processor_Send_Chat_Webhook_test.py index 05e6c6615..814d54ac0 100644 --- a/tests/batch/processor_Send_Chat_Webhook_test.py +++ b/tests/batch/processor_Send_Chat_Webhook_test.py @@ -20,7 +20,7 @@ import json import logging from unittest import mock -from unittest.mock import AsyncMock +from unittest.mock import MagicMock import pytest from sqlalchemy import select @@ -53,10 +53,8 @@ class TestProcessorSendChatWebhook: # No unsent hook exists async def test_normal_1(self, processor, async_session, caplog): # Run processor - with mock.patch( - "aiohttp.client.ClientSession.post", AsyncMock(side_effect=None) - ): - await processor.process() + with mock.patch("requests.post", MagicMock(side_effect=None)): + processor.process() await async_session.commit() # Assertion @@ -75,10 +73,8 @@ async def test_normal_2(self, processor, async_session, caplog): await async_session.commit() # Run processor - with mock.patch( - "aiohttp.client.ClientSession.post", AsyncMock(side_effect=None) - ): - await processor.process() + with mock.patch("requests.post", MagicMock(side_effect=None)): + processor.process() await async_session.commit() # Assertion @@ -96,10 +92,8 @@ async def test_normal_3(self, processor, async_session, caplog): await async_session.commit() # Run processor - with mock.patch( - "aiohttp.client.ClientSession.post", AsyncMock(side_effect=Exception()) - ): - await processor.process() + with mock.patch("requests.post", MagicMock(side_effect=Exception())): + processor.process() await async_session.commit() # Assertion @@ -126,13 +120,11 @@ async def test_error_1(self, processor, async_session, caplog): # Run processor with ( - mock.patch( - "aiohttp.client.ClientSession.post", AsyncMock(side_effect=None) - ), + mock.patch("requests.post", MagicMock(side_effect=None)), mock.patch.object(Session, "commit", side_effect=SQLAlchemyError()), pytest.raises(SQLAlchemyError), ): - await processor.process() + processor.process() await async_session.commit() # Assertion diff --git a/uv.lock b/uv.lock index 69f87ce97..dfa26594c 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.10.11" +version = "3.12.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -21,25 +21,28 @@ dependencies = [ { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, + { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/a8/8e2ba36c6e3278d62e0c88aa42bb92ddbef092ac363b390dab4421da5cf5/aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7", size = 7551886, upload-time = "2024-11-13T16:40:33.335Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/16/077057ef3bd684dbf9a8273a5299e182a8d07b4b252503712ff8b5364fd1/aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710", size = 584830, upload-time = "2024-11-13T16:37:49.608Z" }, - { url = "https://files.pythonhosted.org/packages/2c/cf/348b93deb9597c61a51b6682e81f7c7d79290249e886022ef0705d858d90/aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d", size = 397090, upload-time = "2024-11-13T16:37:51.539Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/903df5cd739dfaf5b827b3d8c9d68ff4fcea16a0ca1aeb948c9da30f56c8/aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97", size = 392361, upload-time = "2024-11-13T16:37:53.586Z" }, - { url = "https://files.pythonhosted.org/packages/fb/97/e4792675448a2ac5bd56f377a095233b805dd1315235c940c8ba5624e3cb/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725", size = 1309839, upload-time = "2024-11-13T16:37:55.68Z" }, - { url = "https://files.pythonhosted.org/packages/96/d0/ba19b1260da6fbbda4d5b1550d8a53ba3518868f2c143d672aedfdbc6172/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636", size = 1348116, upload-time = "2024-11-13T16:37:58.232Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b9/15100ee7113a2638bfdc91aecc54641609a92a7ce4fe533ebeaa8d43ff93/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385", size = 1391402, upload-time = "2024-11-13T16:38:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/c5/36/831522618ac0dcd0b28f327afd18df7fb6bbf3eaf302f912a40e87714846/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087", size = 1304239, upload-time = "2024-11-13T16:38:04.195Z" }, - { url = "https://files.pythonhosted.org/packages/60/9f/b7230d0c48b076500ae57adb717aa0656432acd3d8febb1183dedfaa4e75/aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f", size = 1256565, upload-time = "2024-11-13T16:38:07.218Z" }, - { url = "https://files.pythonhosted.org/packages/63/c2/35c7b4699f4830b3b0a5c3d5619df16dca8052ae8b488e66065902d559f6/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03", size = 1269285, upload-time = "2024-11-13T16:38:09.396Z" }, - { url = "https://files.pythonhosted.org/packages/51/48/bc20ea753909bdeb09f9065260aefa7453e3a57f6a51f56f5216adc1a5e7/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d", size = 1276716, upload-time = "2024-11-13T16:38:12.039Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7b/a8708616b3810f55ead66f8e189afa9474795760473aea734bbea536cd64/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a", size = 1315023, upload-time = "2024-11-13T16:38:15.155Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d6/dfe9134a921e05b01661a127a37b7d157db93428905450e32f9898eef27d/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e", size = 1342735, upload-time = "2024-11-13T16:38:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/ca/1a/3bd7f18e3909eabd57e5d17ecdbf5ea4c5828d91341e3676a07de7c76312/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4", size = 1302618, upload-time = "2024-11-13T16:38:19.865Z" }, - { url = "https://files.pythonhosted.org/packages/cf/51/d063133781cda48cfdd1e11fc8ef45ab3912b446feba41556385b3ae5087/aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb", size = 360497, upload-time = "2024-11-13T16:38:21.996Z" }, - { url = "https://files.pythonhosted.org/packages/55/4e/f29def9ed39826fe8f85955f2e42fe5cc0cbe3ebb53c97087f225368702e/aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27", size = 380577, upload-time = "2024-11-13T16:38:24.247Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" }, + { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" }, + { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" }, ] [[package]] @@ -774,7 +777,6 @@ name = "ibet-wallet-api" version = "25.6" source = { virtual = "." } dependencies = [ - { name = "aiohttp" }, { name = "aiomysql" }, { name = "alembic" }, { name = "asyncpg" }, @@ -803,6 +805,7 @@ dependencies = [ { name = "pymysql", extra = ["rsa"] }, { name = "pyroscope-io" }, { name = "pyroscope-otel" }, + { name = "requests" }, { name = "sqlalchemy", extra = ["asyncio"] }, { name = "tzdata" }, { name = "uvicorn", extra = ["standard"] }, @@ -811,6 +814,7 @@ dependencies = [ [package.optional-dependencies] ibet-explorer = [ + { name = "aiohttp" }, { name = "async-cache" }, { name = "ibet-wallet-api-explorer" }, { name = "textual" }, @@ -836,9 +840,10 @@ dev = [ [package.metadata] requires-dist = [ - { name = "aiohttp", specifier = "~=3.10.10" }, + { name = "aiohttp", marker = "extra == 'ibet-explorer'", specifier = "~=3.12.13" }, { name = "aiomysql", specifier = "==0.2.0" }, { name = "alembic", specifier = ">=1.14.0,<2.0.0" }, + { name = "async-cache", marker = "extra == 'ibet-explorer'", specifier = "==1.1.1" }, { name = "async-cache", marker = "extra == 'ibet-explorer'", specifier = "~=1.1.1" }, { name = "asyncpg", specifier = "~=0.30.0" }, { name = "boto3", specifier = "~=1.37.0" }, @@ -867,6 +872,7 @@ requires-dist = [ { name = "pymysql", extras = ["rsa"], specifier = "~=1.1.0" }, { name = "pyroscope-io", specifier = ">=0.8.11" }, { name = "pyroscope-otel", specifier = ">=0.4.1" }, + { name = "requests", specifier = ">=2.32.3" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.33,<3.0.0" }, { name = "textual", marker = "extra == 'ibet-explorer'", specifier = "~=0.44.1" }, { name = "typer", marker = "extra == 'ibet-explorer'", specifier = "~=0.12.3" }, From ca5bd6c0cc1e7e022d6cd7f893f33a7b3f4552b0 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Wed, 2 Jul 2025 08:23:58 +0900 Subject: [PATCH 03/22] Update transfer tests and models to include 'ibet_wst_bridge' message (#1655) --- app/model/db/idx_transfer.py | 3 ++- app/model/schema/token.py | 2 ++ tests/batch/indexer_Transfer_test.py | 18 +++++++++--------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/model/db/idx_transfer.py b/app/model/db/idx_transfer.py index 16d87f058..103cd1ff0 100644 --- a/app/model/db/idx_transfer.py +++ b/app/model/db/idx_transfer.py @@ -47,6 +47,7 @@ class DataMessage(BaseModel): "garnishment", "inheritance", "force_unlock", + "ibet_wst_bridge", ] @@ -81,7 +82,7 @@ class IDXTransfer(Base): # source_event = "Transfer" # => None # source_event = "Unlock" - # => "force_unlock", "garnishment" or "inheritance" + # => "force_unlock", "garnishment", "inheritance" or "ibet_wst_bridge" message: Mapped[str | None] = mapped_column(String(50), index=True) @staticmethod diff --git a/app/model/schema/token.py b/app/model/schema/token.py index a6b129f04..b1cc69d97 100644 --- a/app/model/schema/token.py +++ b/app/model/schema/token.py @@ -407,6 +407,7 @@ class TransferHistoryBase(BaseModel): "garnishment", "inheritance", "force_unlock", + "ibet_wst_bridge", ] | None ) @@ -427,6 +428,7 @@ class DataMessage(BaseModel): "garnishment", "inheritance", "force_unlock", + "ibet_wst_bridge", ] diff --git a/tests/batch/indexer_Transfer_test.py b/tests/batch/indexer_Transfer_test.py index 9b9c64795..7397a12f2 100644 --- a/tests/batch/indexer_Transfer_test.py +++ b/tests/batch/indexer_Transfer_test.py @@ -568,7 +568,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): target=self.trader["account_address"], recipient=self.trader2["account_address"], amount=10000, - data_str=json.dumps({"message": "force_unlock"}), + data_str=json.dumps({"message": "ibet_wst_bridge"}), ) block_number_6 = web3.eth.block_number share_force_change_locked_account( @@ -578,7 +578,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): before_account_address=self.trader["account_address"], after_account_address=self.trader2["account_address"], amount=10000, - data_str=json.dumps({"message": "force_change"}), + data_str=json.dumps({"message": "ibet_wst_bridge"}), ) block_number_7 = web3.eth.block_number share_force_change_locked_account( @@ -588,7 +588,7 @@ async def test_normal_2(self, processor, shared_contract, async_session): before_account_address=self.trader["account_address"], after_account_address=self.trader2["account_address"], amount=10000, - data_str="", + data_str=json.dumps({"message": "ibet_wst_bridge"}), ) block_number_8 = web3.eth.block_number @@ -680,8 +680,8 @@ async def test_normal_2(self, processor, shared_contract, async_session): assert idx_transfer.to_address == self.trader2["account_address"] assert idx_transfer.value == 10000 assert idx_transfer.source_event == IDXTransferSourceEventType.FORCE_UNLOCK - assert idx_transfer.data == {"message": "force_unlock"} - assert idx_transfer.message == "force_unlock" + assert idx_transfer.data == {"message": "ibet_wst_bridge"} + assert idx_transfer.message == "ibet_wst_bridge" assert idx_transfer.created is not None assert idx_transfer.modified is not None @@ -697,8 +697,8 @@ async def test_normal_2(self, processor, shared_contract, async_session): idx_transfer.source_event == IDXTransferSourceEventType.FORCE_CHANGE_LOCKED_ACCOUNT ) - assert idx_transfer.data == {} - assert idx_transfer.message is None + assert idx_transfer.data == {"message": "ibet_wst_bridge"} + assert idx_transfer.message == "ibet_wst_bridge" assert idx_transfer.created is not None assert idx_transfer.modified is not None @@ -714,8 +714,8 @@ async def test_normal_2(self, processor, shared_contract, async_session): idx_transfer.source_event == IDXTransferSourceEventType.FORCE_CHANGE_LOCKED_ACCOUNT ) - assert idx_transfer.data == {} - assert idx_transfer.message is None + assert idx_transfer.data == {"message": "ibet_wst_bridge"} + assert idx_transfer.message == "ibet_wst_bridge" assert idx_transfer.created is not None assert idx_transfer.modified is not None From 0ab82540efa4fe0beb5b46fc3c35d9ba78736a72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 08:00:43 +0900 Subject: [PATCH 04/22] Bump hardhat from 2.25.0 to 2.26.1 (#1656) Bumps [hardhat](https://github.com/nomiclabs/hardhat) from 2.25.0 to 2.26.1. - [Release notes](https://github.com/nomiclabs/hardhat/releases) - [Commits](https://github.com/nomiclabs/hardhat/compare/hardhat@2.25.0...hardhat@2.26.1) --- updated-dependencies: - dependency-name: hardhat dependency-version: 2.26.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 108 ++++++++++++++++------------------------------ package.json | 2 +- 2 files changed, 37 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 389b94007..6d0cf8de5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "hardhat": "^2.25.0" + "hardhat": "^2.26.1" } }, "node_modules/@ethereumjs/rlp": { @@ -579,28 +579,28 @@ "license": "MIT" }, "node_modules/@nomicfoundation/edr": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.11.1.tgz", - "integrity": "sha512-P97XwcD9DdMMZm9aqw89+mzqzlKmqzSPM3feBES2WVRm5/LOiBYorhpeAX+ANj0X8532SKgxoZK/CN5OWv9vZA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.11.3.tgz", + "integrity": "sha512-kqILRkAd455Sd6v8mfP3C1/0tCOynJWY+Ir+k/9Boocu2kObCrsFgG+ZWB7fSBVdd9cPVSNrnhWS+V+PEo637g==", "dev": true, "license": "MIT", "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.11.1", - "@nomicfoundation/edr-darwin-x64": "0.11.1", - "@nomicfoundation/edr-linux-arm64-gnu": "0.11.1", - "@nomicfoundation/edr-linux-arm64-musl": "0.11.1", - "@nomicfoundation/edr-linux-x64-gnu": "0.11.1", - "@nomicfoundation/edr-linux-x64-musl": "0.11.1", - "@nomicfoundation/edr-win32-x64-msvc": "0.11.1" + "@nomicfoundation/edr-darwin-arm64": "0.11.3", + "@nomicfoundation/edr-darwin-x64": "0.11.3", + "@nomicfoundation/edr-linux-arm64-gnu": "0.11.3", + "@nomicfoundation/edr-linux-arm64-musl": "0.11.3", + "@nomicfoundation/edr-linux-x64-gnu": "0.11.3", + "@nomicfoundation/edr-linux-x64-musl": "0.11.3", + "@nomicfoundation/edr-win32-x64-msvc": "0.11.3" }, "engines": { "node": ">= 18" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.11.1.tgz", - "integrity": "sha512-vjca7gkl1o0yYqMjwxQpMEtdsb20nWHBnnxDO8ZBCTD5IwfYT5LiMxFaJo8NUJ7ODIRkF/zuEtAF3W7+ZlC5RA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.11.3.tgz", + "integrity": "sha512-w0tksbdtSxz9nuzHKsfx4c2mwaD0+l5qKL2R290QdnN9gi9AV62p9DHkOgfBdyg6/a6ZlnQqnISi7C9avk/6VA==", "dev": true, "license": "MIT", "engines": { @@ -608,9 +608,9 @@ } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.11.1.tgz", - "integrity": "sha512-0aGStHq9XePXX9UqdU1w60HGO9AfYCgkNEir5sBpntU5E0TvZEK6jwyYr667+s90n2mihdeP97QSA0O/6PT6PA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.11.3.tgz", + "integrity": "sha512-QR4jAFrPbOcrO7O2z2ESg+eUeIZPe2bPIlQYgiJ04ltbSGW27FblOzdd5+S3RoOD/dsZGKAvvy6dadBEl0NgoA==", "dev": true, "license": "MIT", "engines": { @@ -618,9 +618,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.11.1.tgz", - "integrity": "sha512-OWhCETc03PVdtzatW/c2tpOPx+GxlBfBaLmMuGRD1soAr1nMOmg2WZAlo4i6Up9fkQYl+paiYMMFVat1meaMvQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.11.3.tgz", + "integrity": "sha512-Ktjv89RZZiUmOFPspuSBVJ61mBZQ2+HuLmV67InNlh9TSUec/iDjGIwAn59dx0bF/LOSrM7qg5od3KKac4LJDQ==", "dev": true, "license": "MIT", "engines": { @@ -628,9 +628,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.11.1.tgz", - "integrity": "sha512-p0qvtIvDA2eZ8pQ5XUKnWdW1IrwFzSrjyrO88oYx6Lkw8nYwf2JEeETo5o5W84DDfimfoBGP7RWPTPcTBKCaLQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.11.3.tgz", + "integrity": "sha512-B3sLJx1rL2E9pfdD4mApiwOZSrX0a/KQSBWdlq1uAhFKqkl00yZaY4LejgZndsJAa4iKGQJlGnw4HCGeVt0+jA==", "dev": true, "license": "MIT", "engines": { @@ -638,9 +638,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.11.1.tgz", - "integrity": "sha512-V4Us7Q0E8kng3O/czd5GRcxmZxWX+USgqz9yQ3o7DVq7FP96idaKvtcbMQp64tjHf2zNtX2y77sGzgbVau7Bww==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.11.3.tgz", + "integrity": "sha512-D/4cFKDXH6UYyKPu6J3Y8TzW11UzeQI0+wS9QcJzjlrrfKj0ENW7g9VihD1O2FvXkdkTjcCZYb6ai8MMTCsaVw==", "dev": true, "license": "MIT", "engines": { @@ -648,9 +648,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.11.1.tgz", - "integrity": "sha512-lCSXsF10Kjjvs5duGbM6pi1WciWHXFNWkMgDAY4pg6ZRIy4gh+uGC6CONMfP4BDZwfrALo2p6+LwyotrJEqpyg==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.11.3.tgz", + "integrity": "sha512-ergXuIb4nIvmf+TqyiDX5tsE49311DrBky6+jNLgsGDTBaN1GS3OFwFS8I6Ri/GGn6xOaT8sKu3q7/m+WdlFzg==", "dev": true, "license": "MIT", "engines": { @@ -658,9 +658,9 @@ } }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.11.1.tgz", - "integrity": "sha512-sNSmmRTURAd1sdKuyO5tqrFiJvHHVPZLM4HB53F21makGoyInFGhejdo3qZrkoinM8k0ewEJDbUp0YuMEgMOhQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.11.3.tgz", + "integrity": "sha512-snvEf+WB3OV0wj2A7kQ+ZQqBquMcrozSLXcdnMdEl7Tmn+KDCbmFKBt3Tk0X3qOU4RKQpLPnTxdM07TJNVtung==", "dev": true, "license": "MIT", "engines": { @@ -917,33 +917,6 @@ "node": ">=6" } }, - "node_modules/@types/bn.js": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.6.tgz", - "integrity": "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.13.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", - "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, "node_modules/adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", @@ -1642,19 +1615,17 @@ "license": "ISC" }, "node_modules/hardhat": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.25.0.tgz", - "integrity": "sha512-yBiA74Yj3VnTRj7lhnn8GalvBdvsMOqTKRrRATSy/2v0VIR2hR0Jcnmfn4aQBLtGAnr3Q2c8CxL0g3LYegUp+g==", + "version": "2.26.1", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.26.1.tgz", + "integrity": "sha512-CXWuUaTtehxiHPCdlitntctfeYRgujmXkNX5gnrD5jdA6HhRQt+WWBZE/gHXbE29y/wDmmUL2d652rI0ctjqjw==", "dev": true, "license": "MIT", "dependencies": { "@ethereumjs/util": "^9.1.0", "@ethersproject/abi": "^5.1.2", - "@nomicfoundation/edr": "^0.11.1", + "@nomicfoundation/edr": "^0.11.3", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", "adm-zip": "^0.4.16", "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", @@ -2742,13 +2713,6 @@ "node": ">=14.0" } }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index e22013225..3827c376d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "devDependencies": { - "hardhat": "^2.25.0" + "hardhat": "^2.26.1" } } From 13094563ab7a95c53a2e781a9fd5fc746f08c2d9 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:30:46 +0900 Subject: [PATCH 05/22] Add reallocation functionality to transfer process (#1659) * Add reallocation functionality to transfer process and update related tests * Update API doc * Update API doc --- app/model/db/idx_transfer.py | 13 +- app/model/schema/token.py | 20 +- batch/indexer_Transfer.py | 22 +- docs/ibet_wallet_api.yaml | 301 +++++++++++++++------------ tests/batch/indexer_Transfer_test.py | 44 +++- tests/contract_modules.py | 23 ++ 6 files changed, 266 insertions(+), 157 deletions(-) diff --git a/app/model/db/idx_transfer.py b/app/model/db/idx_transfer.py index 103cd1ff0..a4898eac5 100644 --- a/app/model/db/idx_transfer.py +++ b/app/model/db/idx_transfer.py @@ -40,6 +40,7 @@ class IDXTransferSourceEventType(StrEnum): UNLOCK = "Unlock" FORCE_UNLOCK = "ForceUnlock" FORCE_CHANGE_LOCKED_ACCOUNT = "ForceChangeLockedAccount" + REALLOCATION = "Reallocation" class DataMessage(BaseModel): @@ -73,16 +74,18 @@ class IDXTransfer(Base): String(50), nullable=False ) # Data - # source_event = "Transfer" + # source_event = "Transfer", "Reallocation" # => None - # source_event = "Unlock" + # source_event = "Unlock", "ForceUnlock", "ForceChangeLockedAccount" # => DataMessage data: Mapped[dict | None] = mapped_column(JSON) # Message - # source_event = "Transfer" + # source_event = "Transfer", "Reallocation" # => None - # source_event = "Unlock" - # => "force_unlock", "garnishment", "inheritance" or "ibet_wst_bridge" + # source_event = "Unlock", "ForceUnlock" + # => "force_unlock", "garnishment" or "inheritance" + # source_event = "ForceChangeLockedAccount" + # => "ibet_wst_bridge" message: Mapped[str | None] = mapped_column(String(50), index=True) @staticmethod diff --git a/app/model/schema/token.py b/app/model/schema/token.py index b1cc69d97..070275aaf 100644 --- a/app/model/schema/token.py +++ b/app/model/schema/token.py @@ -41,6 +41,8 @@ class TransferSourceEvent(StrEnum): Transfer = "Transfer" Unlock = "Unlock" ForceUnlock = "ForceUnlock" + ForceChangeLockedAccount = "ForceChangeLockedAccount" + Reallocation = "Reallocation" ############################ @@ -417,13 +419,13 @@ class TransferHistoryBase(BaseModel): class TransferHistory(TransferHistoryBase): - source_event: Literal[TransferSourceEvent.Transfer] = Field( - description="Source Event" - ) + source_event: Literal[ + TransferSourceEvent.Transfer, TransferSourceEvent.Reallocation + ] = Field(description="Source Event") data: None = Field(description="Event data") -class DataMessage(BaseModel): +class TransferDataMessage(BaseModel): message: Literal[ "garnishment", "inheritance", @@ -432,16 +434,18 @@ class DataMessage(BaseModel): ] -class UnlockTransferHistory(TransferHistoryBase): +class TransferWithMessage(TransferHistoryBase): source_event: Literal[ - TransferSourceEvent.Unlock, TransferSourceEvent.ForceUnlock + TransferSourceEvent.Unlock, + TransferSourceEvent.ForceUnlock, + TransferSourceEvent.ForceChangeLockedAccount, ] = Field(description="Source Event") - data: DataMessage | dict = Field(description="Event data") + data: TransferDataMessage | dict = Field(description="Event data") class TransferHistoriesResponse(BaseModel): result_set: ResultSet - transfer_history: list[TransferHistory | UnlockTransferHistory] = Field( + transfer_history: list[TransferHistory | TransferWithMessage] = Field( description="Transfer history" ) diff --git a/batch/indexer_Transfer.py b/batch/indexer_Transfer.py index 47c6ee9b7..70e711f1b 100644 --- a/batch/indexer_Transfer.py +++ b/batch/indexer_Transfer.py @@ -24,6 +24,7 @@ from typing import List, Optional, Sequence from eth_utils import to_checksum_address +from hexbytes import HexBytes from pydantic import ValidationError from sqlalchemy import desc, select from sqlalchemy.exc import SQLAlchemyError @@ -376,14 +377,31 @@ async def __sync_transfer( f"Skip Registry Transfer data in DB: blockNumber={event['blockNumber']}" ) continue + + # Judge whether the transfer is a reallocation + transaction_hash = event["transactionHash"].to_0x_hex() + is_reallocation = False + tx = await async_web3.eth.get_transaction(transaction_hash) + tx_data: HexBytes | None = tx.get("input") + # Check if the transaction data contains the reallocation marker("c0ffee00") + if "c0ffee00" in tx_data.hex(): + try: + raw_call_data = tx_data.hex().split("c0ffee00", 1)[1] + call_data = json.loads(bytes.fromhex(raw_call_data)) + if call_data.get("purpose") == "Reallocation": + is_reallocation = True + except (ValueError, json.JSONDecodeError): + pass self.__insert_idx( db_session=db_session, - transaction_hash=event["transactionHash"].to_0x_hex(), + transaction_hash=transaction_hash, token_address=to_checksum_address(token.address), from_account_address=args.get("from", ZERO_ADDRESS), to_account_address=args.get("to", ZERO_ADDRESS), value=value, - source_event=IDXTransferSourceEventType.TRANSFER, + source_event=IDXTransferSourceEventType.REALLOCATION + if is_reallocation is True + else IDXTransferSourceEventType.TRANSFER, data_str=None, event_created=event_created, ) diff --git a/docs/ibet_wallet_api.yaml b/docs/ibet_wallet_api.yaml index 695d7f709..b1b940ca7 100644 --- a/docs/ibet_wallet_api.yaml +++ b/docs/ibet_wallet_api.yaml @@ -1,8 +1,8 @@ openapi: 3.1.0 info: title: ibet Wallet API - description: RPC services that provides utility tools for building a wallet system - on ibet network + description: RPC services that provides utility tools for building a wallet + system on ibet network version: 25.6.0 paths: /: @@ -18,7 +18,8 @@ paths: get: tags: - public_info - summary: Information on issued tokens and associated institutions (key managers) + summary: Information on issued tokens and associated institutions (key + managers) description: List issued tokens and associated institutions (key managers) operationId: ListAllPublicListedTokens parameters: @@ -1070,7 +1071,8 @@ paths: tags: - eth_rpc summary: Send signed transaction - description: Sends raw transaction without waiting for transaction receipt. + description: Sends raw transaction without waiting for transaction + receipt. operationId: SendRawTransactionNoWait requestBody: content: @@ -2965,8 +2967,8 @@ paths: tags: - token_info summary: Search Token holders - description: Returns a list of token holders for a given token using detailed - search query. + description: Returns a list of token holders for a given token using + detailed search query. operationId: SearchTokenHolders parameters: - name: token_address @@ -3069,7 +3071,8 @@ paths: tags: - token_info summary: Execute Batch Getting Token Holders At Specific BlockNumber - description: Enqueues task of collecting token holders for a given block number. + description: Enqueues task of collecting token holders for a given block + number. operationId: TokenHoldersCollection parameters: - name: token_address @@ -3130,13 +3133,13 @@ paths: schema: type: string format: uuid - description: Unique id to be assigned to each token holder list.This must - be Version4 UUID. + description: Unique id to be assigned to each token holder list.This + must be Version4 UUID. examples: - cfd83622-34dc-4efe-a68b-2cc275d3d824 title: List Id - description: Unique id to be assigned to each token holder list.This must - be Version4 UUID. + description: Unique id to be assigned to each token holder list.This + must be Version4 UUID. responses: '200': description: Successful Response @@ -3496,8 +3499,8 @@ paths: tags: - token_info summary: Search Token Transfer History - description: Returns a list of transfer histories for a given token using detailed - search query. + description: Returns a list of transfer histories for a given token using + detailed search query. operationId: SearchTokenTransferHistory parameters: - name: token_address @@ -3541,7 +3544,8 @@ paths: tags: - token_info summary: Token Transfer Approval History - description: Returns a list of transfer approval histories for a given token. + description: Returns a list of transfer approval histories for a given + token. operationId: ListTokenTransferApprovalHistory parameters: - name: token_address @@ -3654,8 +3658,8 @@ paths: tags: - token_info summary: Search Token Transfer Approval History - description: Returns a list of transfer approval histories for a given token - using detailed search query. + description: Returns a list of transfer approval histories for a given + token using detailed search query. operationId: SearchTokenTransferApprovalHistory parameters: - name: token_address @@ -5237,8 +5241,8 @@ paths: tags: - messaging summary: Retrieve message encryption key - description: Returns encryption key for use in e2e messaging for given account - address. + description: Returns encryption key for use in e2e messaging for given + account address. operationId: EncryptionKey parameters: - name: account_address @@ -5741,10 +5745,10 @@ paths: required: true schema: $ref: '#/components/schemas/OrderType' - description: Order type to be executed by the Orderer. If 'buy' is selected, - the sell order book will be returned. - description: Order type to be executed by the Orderer. If 'buy' is selected, - the sell order book will be returned. + description: Order type to be executed by the Orderer. If 'buy' is + selected, the sell order book will be returned. + description: Order type to be executed by the Orderer. If 'buy' is + selected, the sell order book will be returned. - name: account_address in: query required: false @@ -5752,11 +5756,11 @@ paths: anyOf: - type: string - type: 'null' - description: Orderer's account address. Orders from the given address - will not be included in the response. + description: Orderer's account address. Orders from the given + address will not be included in the response. title: Account Address - description: Orderer's account address. Orders from the given address will - not be included in the response. + description: Orderer's account address. Orders from the given address + will not be included in the response. responses: '200': description: Successful Response @@ -5879,10 +5883,10 @@ paths: required: true schema: $ref: '#/components/schemas/OrderType' - description: Order type to be executed by the Orderer. If 'buy' is selected, - the sell order book will be returned. - description: Order type to be executed by the Orderer. If 'buy' is selected, - the sell order book will be returned. + description: Order type to be executed by the Orderer. If 'buy' is + selected, the sell order book will be returned. + description: Order type to be executed by the Orderer. If 'buy' is + selected, the sell order book will be returned. - name: account_address in: query required: false @@ -5890,11 +5894,11 @@ paths: anyOf: - type: string - type: 'null' - description: Orderer's account address. Orders from the given address - will not be included in the response. + description: Orderer's account address. Orders from the given + address will not be included in the response. title: Account Address - description: Orderer's account address. Orders from the given address will - not be included in the response. + description: Orderer's account address. Orders from the given address + will not be included in the response. responses: '200': description: Successful Response @@ -6013,9 +6017,11 @@ paths: anyOf: - type: boolean - type: 'null' - description: Whether to include canceled orders or canceled agreements. + description: Whether to include canceled orders or canceled + agreements. title: Include Canceled Items - description: Whether to include canceled orders or canceled agreements. + description: Whether to include canceled orders or canceled + agreements. responses: '200': description: Successful Response @@ -6060,9 +6066,11 @@ paths: anyOf: - type: boolean - type: 'null' - description: Whether to include canceled orders or canceled agreements. + description: Whether to include canceled orders or canceled + agreements. title: Include Canceled Items - description: Whether to include canceled orders or canceled agreements. + description: Whether to include canceled orders or canceled + agreements. responses: '200': description: Successful Response @@ -6115,9 +6123,11 @@ paths: anyOf: - type: boolean - type: 'null' - description: Whether to include canceled orders or canceled agreements. + description: Whether to include canceled orders or canceled + agreements. title: Include Canceled Items - description: Whether to include canceled orders or canceled agreements. + description: Whether to include canceled orders or canceled + agreements. responses: '200': description: Successful Response @@ -6922,8 +6932,8 @@ components: type: string format: uuid4 title: List Id - description: Unique id to be assigned to each token holder list.This must - be Version4 UUID. + description: Unique id to be assigned to each token holder list.This + must be Version4 UUID. examples: - cfd83622-34dc-4efe-a68b-2cc275d3d824 block_number: @@ -6941,8 +6951,8 @@ components: type: string format: uuid4 title: List Id - description: Unique id to be assigned to each token holder list.This must - be Version4 UUID. + description: Unique id to be assigned to each token holder list.This + must be Version4 UUID. examples: - cfd83622-34dc-4efe-a68b-2cc275d3d824 status: @@ -6985,19 +6995,6 @@ components: required: - meta title: DataConflictErrorResponse - DataMessage: - properties: - message: - type: string - enum: - - garnishment - - inheritance - - force_unlock - title: Message - type: object - required: - - message - title: DataMessage DataNotExistsErrorMetainfo: properties: code: @@ -7122,7 +7119,8 @@ components: required: - result_set - positions - title: GenericSecurityTokenPositionsResponse[RetrieveStraightBondTokenResponse] + title: + GenericSecurityTokenPositionsResponse[RetrieveStraightBondTokenResponse] GenericSuccessResponse_ABI_: properties: meta: @@ -7432,7 +7430,8 @@ components: required: - meta - data - title: GenericSuccessResponse[ListAllLockEventsResponse[RetrieveShareTokenResponse]] + title: + GenericSuccessResponse[ListAllLockEventsResponse[RetrieveShareTokenResponse]] GenericSuccessResponse_ListAllLockEventsResponse_RetrieveStraightBondTokenResponse__: properties: meta: @@ -7533,7 +7532,8 @@ components: required: - meta - data - title: GenericSuccessResponse[ListAllOrderListResponse[RetrieveCouponTokenResponse]] + title: + GenericSuccessResponse[ListAllOrderListResponse[RetrieveCouponTokenResponse]] GenericSuccessResponse_ListAllOrderListResponse_RetrieveMembershipTokenResponse__: properties: meta: @@ -7842,7 +7842,8 @@ components: required: - meta - data - title: GenericSuccessResponse[RetrievePaymentAccountRegistrationStatusResponse] + title: + GenericSuccessResponse[RetrievePaymentAccountRegistrationStatusResponse] GenericSuccessResponse_RetrievePersonalInfoRegistrationStatusResponse_: properties: meta: @@ -7856,7 +7857,8 @@ components: required: - meta - data - title: GenericSuccessResponse[RetrievePersonalInfoRegistrationStatusResponse] + title: + GenericSuccessResponse[RetrievePersonalInfoRegistrationStatusResponse] GenericSuccessResponse_RetrieveShareTokenResponse_: properties: meta: @@ -8847,7 +8849,8 @@ components: type: string format: date-time title: Block Timestamp - description: block_timestamp when Lock log was emitted (local_timezone) + description: block_timestamp when Lock log was emitted + (local_timezone) type: object required: - category @@ -8925,7 +8928,8 @@ components: type: string format: date-time title: Block Timestamp - description: block_timestamp when Lock log was emitted (local_timezone) + description: block_timestamp when Lock log was emitted + (local_timezone) token: $ref: '#/components/schemas/RetrieveShareTokenResponse' description: Token information @@ -8992,7 +8996,8 @@ components: type: string format: date-time title: Block Timestamp - description: block_timestamp when Lock log was emitted (local_timezone) + description: block_timestamp when Lock log was emitted + (local_timezone) token: $ref: '#/components/schemas/RetrieveStraightBondTokenResponse' description: Token information @@ -9674,8 +9679,8 @@ components: - type: boolean - type: 'null' title: Skip Conflict Error - description: If set to `True`, a `200` response will be returned instead - of a `409` error in case of duplicate data. + description: If set to `True`, a `200` response will be returned + instead of a `409` error in case of duplicate data. default: false type: object required: @@ -10585,8 +10590,8 @@ components: - 1 status: $ref: '#/components/schemas/SendRawTransactionStatus' - description: execution failure:0, execution success:1, execution success(pending - transaction):2 + description: execution failure:0, execution success:1, execution + success(pending transaction):2 examples: - 1 transaction_hash: @@ -11097,7 +11102,8 @@ components: hold_balance: type: integer title: Hold Balance - description: Amount of balance.This includes balance/pending_transfer/exchange_balance/exchange_commitment/locked. + description: Amount of balance.This includes + balance/pending_transfer/exchange_balance/exchange_commitment/locked. locked_balance: type: integer title: Locked Balance @@ -11117,7 +11123,8 @@ components: $ref: '#/components/schemas/TokenHoldersCollectionHolder' type: array title: Holders - description: Token holder list.This list is excluding token owner address. + description: Token holder list.This list is excluding token owner + address. type: object required: - status @@ -11324,6 +11331,20 @@ components: - cancelled - transfer_approved title: TransferApprovalHistory + TransferDataMessage: + properties: + message: + type: string + enum: + - garnishment + - inheritance + - force_unlock + - ibet_wst_bridge + title: Message + type: object + required: + - message + title: TransferDataMessage TransferHistoriesResponse: properties: result_set: @@ -11332,7 +11353,7 @@ components: items: anyOf: - $ref: '#/components/schemas/TransferHistory' - - $ref: '#/components/schemas/UnlockTransferHistory' + - $ref: '#/components/schemas/TransferWithMessage' type: array title: Transfer History description: Transfer history @@ -11374,15 +11395,19 @@ components: - garnishment - inheritance - force_unlock + - ibet_wst_bridge - type: 'null' title: Message created: type: string title: Created - description: block_timestamp when Transfer log was emitted (local timezone) + description: block_timestamp when Transfer log was emitted (local + timezone) source_event: type: string - const: Transfer + enum: + - Transfer + - Reallocation title: Source Event description: Source Event type: object @@ -11403,7 +11428,72 @@ components: - Transfer - Unlock - ForceUnlock + - ForceChangeLockedAccount + - Reallocation title: TransferSourceEvent + TransferWithMessage: + properties: + transaction_hash: + type: string + title: Transaction Hash + description: Transaction hash + token_address: + type: string + title: Token Address + description: Token address + from_address: + type: string + title: From Address + description: Account address of transfer source + to_address: + type: string + title: To Address + description: Account address of transfer destination + value: + type: integer + title: Value + description: Transfer quantity + data: + anyOf: + - $ref: '#/components/schemas/TransferDataMessage' + - type: object + title: Data + description: Event data + message: + anyOf: + - type: string + enum: + - garnishment + - inheritance + - force_unlock + - ibet_wst_bridge + - type: 'null' + title: Message + created: + type: string + title: Created + description: block_timestamp when Transfer log was emitted (local + timezone) + source_event: + type: string + enum: + - Unlock + - ForceUnlock + - ForceChangeLockedAccount + title: Source Event + description: Source Event + type: object + required: + - transaction_hash + - token_address + - from_address + - to_address + - value + - data + - message + - created + - source_event + title: TransferWithMessage TxData: properties: hash: @@ -11527,66 +11617,6 @@ components: TxDataResponse: $ref: '#/components/schemas/TxDataDetail' title: TxDataResponse - UnlockTransferHistory: - properties: - transaction_hash: - type: string - title: Transaction Hash - description: Transaction hash - token_address: - type: string - title: Token Address - description: Token address - from_address: - type: string - title: From Address - description: Account address of transfer source - to_address: - type: string - title: To Address - description: Account address of transfer destination - value: - type: integer - title: Value - description: Transfer quantity - data: - anyOf: - - $ref: '#/components/schemas/DataMessage' - - type: object - title: Data - description: Event data - message: - anyOf: - - type: string - enum: - - garnishment - - inheritance - - force_unlock - - type: 'null' - title: Message - created: - type: string - title: Created - description: block_timestamp when Transfer log was emitted (local timezone) - source_event: - type: string - enum: - - Unlock - - ForceUnlock - title: Source Event - description: Source Event - type: object - required: - - transaction_hash - - token_address - - from_address - - to_address - - value - - data - - message - - created - - source_event - title: UnlockTransferHistory UpdateAdminTokenRequest: properties: is_public: @@ -11717,6 +11747,7 @@ tags: - name: contract_log description: Contract event logs - name: dex - description: Trade related functions on IbetExchange (Only for utility tokens) + description: Trade related functions on IbetExchange (Only for utility + tokens) - name: messaging description: Messaging functions with external systems diff --git a/tests/batch/indexer_Transfer_test.py b/tests/batch/indexer_Transfer_test.py index 7397a12f2..c6209ab49 100644 --- a/tests/batch/indexer_Transfer_test.py +++ b/tests/batch/indexer_Transfer_test.py @@ -50,6 +50,7 @@ issue_share_token, membership_issue, membership_register_list, + reallocate_share_token, register_bond_list, register_share_list, share_force_change_locked_account, @@ -234,6 +235,7 @@ async def listing_token(token_address, async_session): # - Unlock # - ForceUnlock # - ForceChangeLockedAccount + # - Reallocation async def test_normal_1_1(self, processor, shared_contract, async_session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -250,6 +252,11 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): personal_info_contract_address, self.issuer["account_address"], ) + PersonalInfoUtils.register( + self.trader2["account_address"], + personal_info_contract_address, + self.issuer["account_address"], + ) # emit "Transfer" transfer_share_token( @@ -308,6 +315,15 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): ) block_number_4 = web3.eth.block_number + # emit "Reallocation" + reallocate_share_token( + invoker=self.trader, + to=self.trader2, + token=share_token, + amount=10000, + ) + block_number_5 = web3.eth.block_number + # Execute batch processing await processor.sync_new_logs() @@ -317,7 +333,7 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): select(IDXTransfer).order_by(IDXTransfer.created) ) ).all() - assert len(idx_transfer_list) == 4 + assert len(idx_transfer_list) == 5 block1 = web3.eth.get_block(block_number_1) idx_transfer: IDXTransfer = idx_transfer_list[0] @@ -332,9 +348,23 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): assert idx_transfer.created is not None assert idx_transfer.modified is not None - block2 = web3.eth.get_block(block_number_2) + block5 = web3.eth.get_block(block_number_5) idx_transfer: IDXTransfer = idx_transfer_list[1] assert idx_transfer.id == 2 + assert idx_transfer.transaction_hash == block5["transactions"][0].to_0x_hex() + assert idx_transfer.token_address == share_token["address"] + assert idx_transfer.from_address == self.trader["account_address"] + assert idx_transfer.to_address == self.trader2["account_address"] + assert idx_transfer.value == 10000 + assert idx_transfer.source_event == IDXTransferSourceEventType.REALLOCATION + assert idx_transfer.data is None + assert idx_transfer.message is None + assert idx_transfer.created is not None + assert idx_transfer.modified is not None + + block2 = web3.eth.get_block(block_number_2) + idx_transfer: IDXTransfer = idx_transfer_list[2] + assert idx_transfer.id == 3 assert idx_transfer.transaction_hash == block2["transactions"][0].to_0x_hex() assert idx_transfer.token_address == share_token["address"] assert idx_transfer.from_address == self.trader["account_address"] @@ -347,8 +377,8 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): assert idx_transfer.modified is not None block3 = web3.eth.get_block(block_number_3) - idx_transfer: IDXTransfer = idx_transfer_list[2] - assert idx_transfer.id == 3 + idx_transfer: IDXTransfer = idx_transfer_list[3] + assert idx_transfer.id == 4 assert idx_transfer.transaction_hash == block3["transactions"][0].to_0x_hex() assert idx_transfer.token_address == share_token["address"] assert idx_transfer.from_address == self.trader["account_address"] @@ -361,8 +391,8 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): assert idx_transfer.modified is not None block4 = web3.eth.get_block(block_number_4) - idx_transfer: IDXTransfer = idx_transfer_list[3] - assert idx_transfer.id == 4 + idx_transfer: IDXTransfer = idx_transfer_list[4] + assert idx_transfer.id == 5 assert idx_transfer.transaction_hash == block4["transactions"][0].to_0x_hex() assert idx_transfer.token_address == share_token["address"] assert idx_transfer.from_address == self.trader["account_address"] @@ -387,7 +417,7 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): .limit(1) ) ).first() - assert idx_block_number.latest_block_number == block_number_4 + assert idx_block_number.latest_block_number == block_number_5 # # IbetShare diff --git a/tests/contract_modules.py b/tests/contract_modules.py index 854df117e..309b38558 100644 --- a/tests/contract_modules.py +++ b/tests/contract_modules.py @@ -473,6 +473,29 @@ def transfer_share_token( ) +# SHAREトークン:移管 +def reallocate_share_token( + invoker: UnitTestAccount, to: UnitTestAccount, token: DeployedContract, amount: int +): + web3.eth.default_account = invoker["account_address"] + TokenContract = Contract.get_contract("IbetShare", token["address"]) + tx = TokenContract.functions.transfer( + to["account_address"], amount + ).build_transaction( + { + "from": invoker["account_address"], + "gas": 6000000, + "gasPrice": 0, + } + ) + marker = b"\xc0\xff\xee\x00" + annotation_data = json.dumps( + {"purpose": "Reallocation"}, separators=(",", ":") + ).encode("utf-8") + tx["data"] += marker.hex() + annotation_data.hex() + web3.eth.send_transaction(tx) + + # SHAREトークン:無効化 def invalidate_share_token(invoker, token): web3.eth.default_account = invoker["account_address"] From 349063e98f5a1606f90a581abfda9c5be2429726 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:30:48 +0900 Subject: [PATCH 06/22] Refactor lock and transfer data models to remove deprecated inheritance message (#1660) * Refactor lock and transfer data models to remove deprecated inheritance message * Update upgrade tests to handle JSON data for MySQL engine * Refactor upgrade tests to standardize SQL queries for lock and unlock tables --- app/model/db/__init__.py | 4 +- app/model/db/idx_lock_unlock.py | 26 ++++- app/model/db/idx_transfer.py | 3 +- batch/indexer_Position_Bond.py | 4 + batch/indexer_Position_Share.py | 4 + batch/indexer_Transfer.py | 4 +- .../819325835c3d_v25_9_0_feature_1657.py | 47 +++++++++ tests/batch/indexer_Position_Bond_test.py | 60 +++++++----- tests/batch/indexer_Position_Share_test.py | 59 ++++++----- tests/batch/indexer_Transfer_test.py | 8 +- tests/migrations/upgrade_test.py | 97 +++++++++++++++++++ 11 files changed, 256 insertions(+), 60 deletions(-) create mode 100644 migrations/versions/819325835c3d_v25_9_0_feature_1657.py diff --git a/app/model/db/__init__.py b/app/model/db/__init__.py index 1cad05374..fdc11c4ea 100644 --- a/app/model/db/__init__.py +++ b/app/model/db/__init__.py @@ -22,7 +22,7 @@ from .idx_agreement import AgreementStatus, IDXAgreement from .idx_block_data import IDXBlockData, IDXBlockDataBlockNumber from .idx_consume_coupon import IDXConsumeCoupon -from .idx_lock_unlock import IDXLock, IDXUnlock +from .idx_lock_unlock import IDXLock, IDXUnlock, LockDataMessage, UnlockDataMessage from .idx_order import IDXOrder from .idx_position import ( IDXLockedPosition, @@ -42,10 +42,10 @@ ) from .idx_token_list_register import IDXTokenListBlockNumber, IDXTokenListRegister from .idx_transfer import ( - DataMessage, IDXTransfer, IDXTransferBlockNumber, IDXTransferSourceEventType, + TransferDataMessage, ) from .idx_transfer_approval import IDXTransferApproval, IDXTransferApprovalBlockNumber from .idx_tx_data import IDXTxData diff --git a/app/model/db/idx_lock_unlock.py b/app/model/db/idx_lock_unlock.py index 3f303c140..62c1e15b9 100644 --- a/app/model/db/idx_lock_unlock.py +++ b/app/model/db/idx_lock_unlock.py @@ -18,8 +18,10 @@ """ from datetime import datetime, timedelta, timezone +from enum import StrEnum from zoneinfo import ZoneInfo +from pydantic import BaseModel from sqlalchemy import JSON, BigInteger, Boolean, DateTime, String from sqlalchemy.orm import Mapped, mapped_column @@ -30,6 +32,26 @@ local_tz = ZoneInfo(TZ) +class LockMessage(StrEnum): + garnishment = "garnishment" + force_lock = "force_lock" + ibet_wst_bridge = "ibet_wst_bridge" + + +class LockDataMessage(BaseModel): + message: LockMessage + + +class UnlockMessage(StrEnum): + garnishment = "garnishment" + force_unlock = "force_unlock" + ibet_wst_bridge = "ibet_wst_bridge" + + +class UnlockDataMessage(BaseModel): + message: UnlockMessage + + class IDXLock(Base): """Token Lock Event (INDEX)""" @@ -55,7 +77,7 @@ class IDXLock(Base): account_address: Mapped[str] = mapped_column(String(42), index=True, nullable=False) # Locked Amount value: Mapped[int] = mapped_column(BigInteger, index=True, nullable=False) - # Data + # Data(LockDataMessage) data: Mapped[dict] = mapped_column(JSON, nullable=False) # Lock Datetime block_timestamp: Mapped[datetime] = mapped_column( @@ -135,7 +157,7 @@ class IDXUnlock(Base): ) # Locked Amount value: Mapped[int] = mapped_column(BigInteger, index=True, nullable=False) - # Data + # Data(UnlockDataMessage) data: Mapped[dict] = mapped_column(JSON, nullable=False) # Lock Datetime block_timestamp: Mapped[datetime] = mapped_column( diff --git a/app/model/db/idx_transfer.py b/app/model/db/idx_transfer.py index a4898eac5..921b8f00b 100644 --- a/app/model/db/idx_transfer.py +++ b/app/model/db/idx_transfer.py @@ -43,10 +43,9 @@ class IDXTransferSourceEventType(StrEnum): REALLOCATION = "Reallocation" -class DataMessage(BaseModel): +class TransferDataMessage(BaseModel): message: Literal[ "garnishment", - "inheritance", "force_unlock", "ibet_wst_bridge", ] diff --git a/batch/indexer_Position_Bond.py b/batch/indexer_Position_Bond.py index 80592bff0..46f9828c0 100644 --- a/batch/indexer_Position_Bond.py +++ b/batch/indexer_Position_Bond.py @@ -43,6 +43,8 @@ IDXPositionBondBlockNumber, IDXUnlock, Listing, + LockDataMessage, + UnlockDataMessage, ) from app.model.schema.base import TokenType from app.utils.asyncio_utils import SemaphoreTaskGroup @@ -1751,6 +1753,7 @@ def __insert_lock_idx( """ try: data = json.loads(data_str) + LockDataMessage.model_validate(data) except Exception: data = {} lock = IDXLock() @@ -1797,6 +1800,7 @@ def __insert_unlock_idx( """ try: data = json.loads(data_str) + UnlockDataMessage.model_validate(data) except: data = {} unlock = IDXUnlock() diff --git a/batch/indexer_Position_Share.py b/batch/indexer_Position_Share.py index 09a5de3c8..a011c6160 100644 --- a/batch/indexer_Position_Share.py +++ b/batch/indexer_Position_Share.py @@ -43,6 +43,8 @@ IDXPositionShareBlockNumber, IDXUnlock, Listing, + LockDataMessage, + UnlockDataMessage, ) from app.model.schema.base import TokenType from app.utils.asyncio_utils import SemaphoreTaskGroup @@ -1751,6 +1753,7 @@ def __insert_lock_idx( """ try: data = json.loads(data_str) + LockDataMessage.model_validate(data) except Exception: data = {} lock = IDXLock() @@ -1797,6 +1800,7 @@ def __insert_unlock_idx( """ try: data = json.loads(data_str) + UnlockDataMessage.model_validate(data) except: data = {} unlock = IDXUnlock() diff --git a/batch/indexer_Transfer.py b/batch/indexer_Transfer.py index 70e711f1b..445d0e49f 100644 --- a/batch/indexer_Transfer.py +++ b/batch/indexer_Transfer.py @@ -37,11 +37,11 @@ from app.database import BatchAsyncSessionLocal from app.errors import ServiceUnavailable from app.model.db import ( - DataMessage, IDXTransfer, IDXTransferBlockNumber, IDXTransferSourceEventType, Listing, + TransferDataMessage, ) from app.model.schema.base import TokenType from app.utils.web3_utils import AsyncWeb3Wrapper @@ -183,7 +183,7 @@ def __insert_idx( if data_str is not None: try: data = json.loads(data_str) - validated_data = DataMessage(**data) + validated_data = TransferDataMessage(**data) message = validated_data.message except ValidationError: data = {} diff --git a/migrations/versions/819325835c3d_v25_9_0_feature_1657.py b/migrations/versions/819325835c3d_v25_9_0_feature_1657.py new file mode 100644 index 000000000..463cbccf7 --- /dev/null +++ b/migrations/versions/819325835c3d_v25_9_0_feature_1657.py @@ -0,0 +1,47 @@ +"""v25_9_0_feature_1657 + +Revision ID: 819325835c3d +Revises: 9a28ed8d4afd +Create Date: 2025-07-23 18:08:21.277260 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy import update, cast, String + + +from app.database import get_db_schema +from app.model.db import IDXTransfer, IDXLock, IDXUnlock + +# revision identifiers, used by Alembic. +revision = "819325835c3d" +down_revision = "9a28ed8d4afd" +branch_labels = None +depends_on = None + + +def upgrade(): + connection = op.get_bind() + + op.get_bind().execute( + update(IDXTransfer) + .values(message=None, data={}) + .where(IDXTransfer.message == "inheritance") + ) + op.get_bind().execute( + update(IDXLock) + .values(data={}) + .where(cast(IDXLock.data, String).like(f"%inheritance%")) + ) + op.get_bind().execute( + update(IDXUnlock) + .values(data={}) + .where(cast(IDXUnlock.data, String).like(f"%inheritance%")) + ) + + +def downgrade(): + connection = op.get_bind() + + pass diff --git a/tests/batch/indexer_Position_Bond_test.py b/tests/batch/indexer_Position_Bond_test.py index afd8e891b..b7ba56312 100644 --- a/tests/batch/indexer_Position_Bond_test.py +++ b/tests/batch/indexer_Position_Bond_test.py @@ -515,10 +515,13 @@ async def test_normal_4_1(self, processor, shared_contract, session): # Lock token_contract.functions.lock( - self.trader["account_address"], 1500, '{"message": "locked1"}' + self.trader["account_address"], 1500, '{"message": "garnishment"}' ).transact({"from": self.issuer["account_address"]}) token_contract.functions.lock( - self.trader["account_address"], 1500, '{"message": "locked2"}' + self.trader["account_address"], 1500, '{"message": "ibet_wst_bridge"}' + ).transact({"from": self.issuer["account_address"]}) + token_contract.functions.lock( + self.trader["account_address"], 1500, '{"message": "inheritance"}' ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -541,7 +544,7 @@ async def test_normal_4_1(self, processor, shared_contract, session): _position = _position_list[0] assert _position.token_address == token["address"] assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 3000 + assert _position.balance == 1000000 - 4500 assert _position.pending_transfer == 0 assert _position.exchange_balance == 0 assert _position.exchange_commitment == 0 @@ -554,12 +557,12 @@ async def test_normal_4_1(self, processor, shared_contract, session): assert _locked1.token_address == token["address"] assert _locked1.lock_address == self.trader["account_address"] assert _locked1.account_address == self.issuer["account_address"] - assert _locked1.value == 3000 + assert _locked1.value == 4500 _lock_list: Sequence[IDXLock] = session.scalars( select(IDXLock).order_by(IDXLock.id) ).all() - assert len(_lock_list) == 2 + assert len(_lock_list) == 3 _lock1 = _lock_list[0] assert _lock1.id == 1 assert _lock1.token_address == token["address"] @@ -567,7 +570,7 @@ async def test_normal_4_1(self, processor, shared_contract, session): assert _lock1.lock_address == self.trader["account_address"] assert _lock1.account_address == self.issuer["account_address"] assert _lock1.value == 1500 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} assert _lock1.is_forced is False _lock2 = _lock_list[1] assert _lock2.id == 2 @@ -576,8 +579,17 @@ async def test_normal_4_1(self, processor, shared_contract, session): assert _lock2.lock_address == self.trader["account_address"] assert _lock2.account_address == self.issuer["account_address"] assert _lock2.value == 1500 - assert _lock2.data == {"message": "locked2"} + assert _lock2.data == {"message": "ibet_wst_bridge"} assert _lock2.is_forced is False + _lock3 = _lock_list[2] + assert _lock3.id == 3 + assert _lock3.token_address == token["address"] + assert _lock3.msg_sender == self.issuer["account_address"] + assert _lock3.lock_address == self.trader["account_address"] + assert _lock3.account_address == self.issuer["account_address"] + assert _lock3.value == 1500 + assert _lock3.data == {} + assert _lock3.is_forced is False # # Single Token @@ -611,13 +623,13 @@ async def test_normal_4_2(self, processor, shared_contract, session): self.issuer["account_address"], self.trader["account_address"], 1500, - '{"message": "force_locked1"}', + '{"message": "force_lock"}', ).transact({"from": self.issuer["account_address"]}) token_contract.functions.forceLock( self.issuer["account_address"], self.trader["account_address"], 1500, - '{"message": "force_locked2"}', + '{"message": "force_lock"}', ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -666,7 +678,7 @@ async def test_normal_4_2(self, processor, shared_contract, session): assert _lock1.lock_address == self.issuer["account_address"] assert _lock1.account_address == self.trader["account_address"] assert _lock1.value == 1500 - assert _lock1.data == {"message": "force_locked1"} + assert _lock1.data == {"message": "force_lock"} assert _lock1.is_forced is True _lock2 = _lock_list[1] assert _lock2.id == 2 @@ -675,7 +687,7 @@ async def test_normal_4_2(self, processor, shared_contract, session): assert _lock2.lock_address == self.issuer["account_address"] assert _lock2.account_address == self.trader["account_address"] assert _lock2.value == 1500 - assert _lock2.data == {"message": "force_locked2"} + assert _lock2.data == {"message": "force_lock"} assert _lock2.is_forced is True # @@ -698,7 +710,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): # Lock token_contract.functions.lock( - self.trader["account_address"], 3000, '{"message": "locked1"}' + self.trader["account_address"], 3000, '{"message": "garnishment"}' ).transact({"from": self.issuer["account_address"]}) # Unlock @@ -706,7 +718,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): self.issuer["account_address"], self.trader2["account_address"], 100, - '{"message": "unlocked1"}', + '{"message": "garnishment"}', ).transact({"from": self.trader["account_address"]}) # Run target process @@ -781,7 +793,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): assert _lock1.lock_address == self.trader["account_address"] assert _lock1.account_address == self.issuer["account_address"] assert _lock1.value == 3000 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} _unlock_list: Sequence[IDXUnlock] = session.scalars( select(IDXUnlock).order_by(IDXUnlock.id) @@ -795,7 +807,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): assert _unlock1.account_address == self.issuer["account_address"] assert _unlock1.recipient_address == self.trader2["account_address"] assert _unlock1.value == 100 - assert _unlock1.data == {"message": "unlocked1"} + assert _unlock1.data == {"message": "garnishment"} assert _unlock1.is_forced is False # @@ -818,7 +830,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): # Lock token_contract.functions.lock( - self.trader["account_address"], 3000, '{"message": "locked1"}' + self.trader["account_address"], 3000, '{"message": "garnishment"}' ).transact({"from": self.issuer["account_address"]}) # ForceUnlock @@ -827,7 +839,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): self.issuer["account_address"], self.trader2["account_address"], 100, - '{"message": "unlocked1"}', + '{"message": "garnishment"}', ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -902,7 +914,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): assert _lock1.lock_address == self.trader["account_address"] assert _lock1.account_address == self.issuer["account_address"] assert _lock1.value == 3000 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} _unlock_list: Sequence[IDXUnlock] = session.scalars( select(IDXUnlock).order_by(IDXUnlock.id) @@ -916,7 +928,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): assert _unlock1.account_address == self.issuer["account_address"] assert _unlock1.recipient_address == self.trader2["account_address"] assert _unlock1.value == 100 - assert _unlock1.data == {"message": "unlocked1"} + assert _unlock1.data == {"message": "garnishment"} assert _unlock1.is_forced is True # @@ -954,7 +966,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): token_contract.functions.lock( self.issuer["account_address"], # lock address 3000, - '{"message": "locked1"}', + '{"message": "garnishment"}', ).transact({"from": self.trader["account_address"]}) # ForceChangeLockedAccount @@ -963,7 +975,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): self.trader["account_address"], # before account address self.trader2["account_address"], # after account address 100, - '{"message": "force_changed"}', + '{"message": "ibet_wst_bridge"}', ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -1026,7 +1038,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): assert _lock1.lock_address == self.issuer["account_address"] assert _lock1.account_address == self.trader["account_address"] assert _lock1.value == 3000 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} _lock2 = _lock_list[1] assert _lock2.id == 2 assert _lock2.token_address == token["address"] @@ -1034,7 +1046,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): assert _lock2.lock_address == self.issuer["account_address"] assert _lock2.account_address == self.trader2["account_address"] assert _lock2.value == 100 - assert _lock2.data == {"message": "force_changed"} + assert _lock2.data == {"message": "ibet_wst_bridge"} _unlock_list: Sequence[IDXUnlock] = session.scalars( select(IDXUnlock).order_by(IDXUnlock.id) @@ -1048,7 +1060,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): assert _unlock1.account_address == self.trader["account_address"] assert _unlock1.recipient_address == self.trader2["account_address"] assert _unlock1.value == 100 - assert _unlock1.data == {"message": "force_changed"} + assert _unlock1.data == {"message": "ibet_wst_bridge"} assert _unlock1.is_forced is True # diff --git a/tests/batch/indexer_Position_Share_test.py b/tests/batch/indexer_Position_Share_test.py index 0f3d62fd0..f18d0df4e 100644 --- a/tests/batch/indexer_Position_Share_test.py +++ b/tests/batch/indexer_Position_Share_test.py @@ -497,10 +497,13 @@ async def test_normal_4_1(self, processor, shared_contract, session): # Lock token_contract.functions.lock( - self.trader["account_address"], 1500, '{"message": "locked1"}' + self.trader["account_address"], 1500, '{"message": "garnishment"}' ).transact({"from": self.issuer["account_address"]}) token_contract.functions.lock( - self.trader["account_address"], 1500, '{"message": "locked2"}' + self.trader["account_address"], 1500, '{"message": "ibet_wst_bridge"}' + ).transact({"from": self.issuer["account_address"]}) + token_contract.functions.lock( + self.trader["account_address"], 1500, '{"message": "inheritance"}' ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -523,7 +526,7 @@ async def test_normal_4_1(self, processor, shared_contract, session): _position = _position_list[0] assert _position.token_address == token["address"] assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 3000 + assert _position.balance == 1000000 - 4500 assert _position.pending_transfer == 0 assert _position.exchange_balance == 0 assert _position.exchange_commitment == 0 @@ -537,10 +540,10 @@ async def test_normal_4_1(self, processor, shared_contract, session): assert _locked1.token_address == token["address"] assert _locked1.lock_address == self.trader["account_address"] assert _locked1.account_address == self.issuer["account_address"] - assert _locked1.value == 3000 + assert _locked1.value == 4500 _lock_list = session.scalars(select(IDXLock).order_by(IDXLock.id)).all() - assert len(_lock_list) == 2 + assert len(_lock_list) == 3 _lock1 = _lock_list[0] assert _lock1.id == 1 @@ -548,7 +551,7 @@ async def test_normal_4_1(self, processor, shared_contract, session): assert _lock1.lock_address == self.trader["account_address"] assert _lock1.account_address == self.issuer["account_address"] assert _lock1.value == 1500 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} assert _lock1.is_forced is False _lock2 = _lock_list[1] assert _lock2.id == 2 @@ -556,8 +559,16 @@ async def test_normal_4_1(self, processor, shared_contract, session): assert _lock2.lock_address == self.trader["account_address"] assert _lock2.account_address == self.issuer["account_address"] assert _lock2.value == 1500 - assert _lock2.data == {"message": "locked2"} + assert _lock2.data == {"message": "ibet_wst_bridge"} assert _lock2.is_forced is False + _lock3 = _lock_list[2] + assert _lock3.id == 3 + assert _lock3.token_address == token["address"] + assert _lock3.lock_address == self.trader["account_address"] + assert _lock3.account_address == self.issuer["account_address"] + assert _lock3.value == 1500 + assert _lock3.data == {} + assert _lock3.is_forced is False # # Single Token @@ -591,13 +602,13 @@ async def test_normal_4_2(self, processor, shared_contract, session): self.issuer["account_address"], self.trader["account_address"], 1500, - '{"message": "force_locked1"}', + '{"message": "force_lock"}', ).transact({"from": self.issuer["account_address"]}) token_contract.functions.forceLock( self.issuer["account_address"], self.trader["account_address"], 1500, - '{"message": "force_locked2"}', + '{"message": "force_lock"}', ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -646,7 +657,7 @@ async def test_normal_4_2(self, processor, shared_contract, session): assert _lock1.lock_address == self.issuer["account_address"] assert _lock1.account_address == self.trader["account_address"] assert _lock1.value == 1500 - assert _lock1.data == {"message": "force_locked1"} + assert _lock1.data == {"message": "force_lock"} assert _lock1.is_forced is True _lock2 = _lock_list[1] assert _lock2.id == 2 @@ -655,7 +666,7 @@ async def test_normal_4_2(self, processor, shared_contract, session): assert _lock2.lock_address == self.issuer["account_address"] assert _lock2.account_address == self.trader["account_address"] assert _lock2.value == 1500 - assert _lock2.data == {"message": "force_locked2"} + assert _lock2.data == {"message": "force_lock"} assert _lock2.is_forced is True # @@ -678,7 +689,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): # Lock token_contract.functions.lock( - self.trader["account_address"], 3000, '{"message": "locked1"}' + self.trader["account_address"], 3000, '{"message": "garnishment"}' ).transact({"from": self.issuer["account_address"]}) # Unlock @@ -686,7 +697,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): self.issuer["account_address"], self.trader2["account_address"], 100, - '{"message": "unlocked1"}', + '{"message": "garnishment"}', ).transact({"from": self.trader["account_address"]}) # Run target process @@ -759,7 +770,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): assert _lock1.lock_address == self.trader["account_address"] assert _lock1.account_address == self.issuer["account_address"] assert _lock1.value == 3000 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} _unlock_list = session.scalars(select(IDXUnlock).order_by(IDXUnlock.id)).all() assert len(_unlock_list) == 1 @@ -771,7 +782,7 @@ async def test_normal_5_1(self, processor, shared_contract, session): assert _unlock1.account_address == self.issuer["account_address"] assert _unlock1.recipient_address == self.trader2["account_address"] assert _unlock1.value == 100 - assert _unlock1.data == {"message": "unlocked1"} + assert _unlock1.data == {"message": "garnishment"} assert _unlock1.is_forced is False # @@ -794,7 +805,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): # Lock token_contract.functions.lock( - self.trader["account_address"], 3000, '{"message": "locked1"}' + self.trader["account_address"], 3000, '{"message": "garnishment"}' ).transact({"from": self.issuer["account_address"]}) # Unlock @@ -803,7 +814,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): self.issuer["account_address"], self.trader2["account_address"], 100, - '{"message": "unlocked1"}', + '{"message": "garnishment"}', ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -876,7 +887,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): assert _lock1.lock_address == self.trader["account_address"] assert _lock1.account_address == self.issuer["account_address"] assert _lock1.value == 3000 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} _unlock_list = session.scalars(select(IDXUnlock).order_by(IDXUnlock.id)).all() assert len(_unlock_list) == 1 @@ -888,7 +899,7 @@ async def test_normal_5_2(self, processor, shared_contract, session): assert _unlock1.account_address == self.issuer["account_address"] assert _unlock1.recipient_address == self.trader2["account_address"] assert _unlock1.value == 100 - assert _unlock1.data == {"message": "unlocked1"} + assert _unlock1.data == {"message": "garnishment"} assert _unlock1.is_forced is True # @@ -925,7 +936,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): token_contract.functions.lock( self.issuer["account_address"], # lock address 3000, - '{"message": "locked1"}', + '{"message": "garnishment"}', ).transact({"from": self.trader["account_address"]}) # ForceChangeLockedAccount @@ -934,7 +945,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): self.trader["account_address"], # before account address self.trader2["account_address"], # after account address 100, - '{"message": "force_changed"}', + '{"message": "ibet_wst_bridge"}', ).transact({"from": self.issuer["account_address"]}) # Run target process @@ -995,7 +1006,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): assert _lock1.lock_address == self.issuer["account_address"] assert _lock1.account_address == self.trader["account_address"] assert _lock1.value == 3000 - assert _lock1.data == {"message": "locked1"} + assert _lock1.data == {"message": "garnishment"} _lock2 = _lock_list[1] assert _lock2.id == 2 assert _lock2.token_address == token["address"] @@ -1003,7 +1014,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): assert _lock2.lock_address == self.issuer["account_address"] assert _lock2.account_address == self.trader2["account_address"] assert _lock2.value == 100 - assert _lock2.data == {"message": "force_changed"} + assert _lock2.data == {"message": "ibet_wst_bridge"} _unlock_list = session.scalars(select(IDXUnlock).order_by(IDXUnlock.id)).all() assert len(_unlock_list) == 1 @@ -1015,7 +1026,7 @@ async def test_normal_5_3(self, processor, shared_contract, session): assert _unlock1.account_address == self.trader["account_address"] assert _unlock1.recipient_address == self.trader2["account_address"] assert _unlock1.value == 100 - assert _unlock1.data == {"message": "force_changed"} + assert _unlock1.data == {"message": "ibet_wst_bridge"} assert _unlock1.is_forced is True # diff --git a/tests/batch/indexer_Transfer_test.py b/tests/batch/indexer_Transfer_test.py index c6209ab49..ec286e0d1 100644 --- a/tests/batch/indexer_Transfer_test.py +++ b/tests/batch/indexer_Transfer_test.py @@ -668,8 +668,8 @@ async def test_normal_2(self, processor, shared_contract, async_session): assert idx_transfer.to_address == self.trader2["account_address"] assert idx_transfer.value == 30000 assert idx_transfer.source_event == IDXTransferSourceEventType.UNLOCK - assert idx_transfer.data == {"message": "inheritance"} - assert idx_transfer.message == "inheritance" + assert idx_transfer.data == {} + assert idx_transfer.message is None assert idx_transfer.created is not None assert idx_transfer.modified is not None @@ -682,8 +682,8 @@ async def test_normal_2(self, processor, shared_contract, async_session): assert idx_transfer.to_address == self.trader2["account_address"] assert idx_transfer.value == 30000 assert idx_transfer.source_event == IDXTransferSourceEventType.UNLOCK - assert idx_transfer.data == {"message": "inheritance"} - assert idx_transfer.message == "inheritance" + assert idx_transfer.data == {} + assert idx_transfer.message is None assert idx_transfer.created is not None assert idx_transfer.modified is not None diff --git a/tests/migrations/upgrade_test.py b/tests/migrations/upgrade_test.py index 2e42c9308..7ad05475d 100644 --- a/tests/migrations/upgrade_test.py +++ b/tests/migrations/upgrade_test.py @@ -18,6 +18,7 @@ """ import importlib +import json import logging from datetime import datetime from typing import Final @@ -39,6 +40,7 @@ REVISION_23_12: Final = "f6f13d28bb48" REVISION_24_3: Final = "3d3b90fda898" REVISION_24_6: Final = "418af51b07b5" +REVISION_25_6: Final = "9a28ed8d4afd" REVISION_UP_TO_1_8 = [REVISION_22_3] REVISION_UP_TO_22_6 = REVISION_UP_TO_1_8 + [REVISION_22_6] @@ -925,3 +927,98 @@ def test_upgrade_v24_6( else: assert share_tokens[0].require_personal_info_registered is True assert share_tokens[1].require_personal_info_registered is True + + def test_upgrade_v25_6( + self, alembic_runner: MigrationContext, caplog: LogCaptureFixture + ): + # 1. Migrate to v25.6 initial + alembic_runner.migrate_up_to(REVISION_25_6) + meta = MetaData() + meta.reflect(bind=engine) + + # 2. Insert test record + idx_transfer = meta.tables.get("transfer") + stmt1 = insert(idx_transfer).values( + id=1, + transaction_hash="test1", + token_address="test1", + from_address="test1", + to_address="test1", + value=1, + source_event="Unlock", + data={"message": "inheritance"}, + message="inheritance", + ) + + idx_lock = meta.tables.get("lock") + stmt2 = insert(idx_lock).values( + id=1, + transaction_hash="test1", + msg_sender="test1", + block_number=1, + token_address="test1", + lock_address="test1", + account_address="test1", + value=1, + data={"message": "inheritance"}, + block_timestamp=datetime(2025, 7, 23, 0, 0, 0), + is_forced=False, + ) + + idx_unlock = meta.tables.get("unlock") + stmt3 = insert(idx_unlock).values( + id=1, + transaction_hash="test1", + msg_sender="test1", + block_number=1, + token_address="test1", + lock_address="test1", + account_address="test1", + recipient_address="test1", + value=1, + data={"message": "inheritance"}, + block_timestamp=datetime(2025, 7, 23, 0, 0, 0), + is_forced=False, + ) + + with engine.connect() as conn: + conn.execute(stmt1) + conn.execute(stmt2) + conn.execute(stmt3) + conn.commit() + + # 3. Run to head + alembic_runner.migrate_up_to("head") + + with engine.connect() as conn: + transfers = conn.execute( + text("SELECT * FROM transfer ORDER BY created ASC") + ) + transfers = list(transfers) + if engine.name == "mysql": + assert json.loads(transfers[0].data) == {} + else: + assert transfers[0].data == {} + assert transfers[0].message is None + + if engine.name == "mysql": + locks = conn.execute(text("SELECT * FROM `lock` ORDER BY created ASC")) + locks = list(locks) + assert json.loads(locks[0].data) == {} + else: + locks = conn.execute(text("SELECT * FROM lock ORDER BY created ASC")) + locks = list(locks) + assert locks[0].data == {} + + if engine.name == "mysql": + unlocks = conn.execute( + text("SELECT * FROM `unlock` ORDER BY created ASC") + ) + unlocks = list(unlocks) + assert json.loads(unlocks[0].data) == {} + else: + unlocks = conn.execute( + text("SELECT * FROM unlock ORDER BY created ASC") + ) + unlocks = list(unlocks) + assert unlocks[0].data == {} From a2a25d2d525dfc06120a0b843590cf8f53c3687f Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:46:21 +0900 Subject: [PATCH 07/22] Add watchers for ChangeToRedeemed and ChangeToCanceled events with notification processing (#1661) --- app/model/db/notification.py | 2 + batch/processor_Notifications_Token.py | 142 ++- .../notification_Notifications_GET_test.py | 4 +- .../processor_Notifications_Token_test.py | 852 ++++++++++++++++++ tests/contract_modules.py | 18 + 5 files changed, 1006 insertions(+), 12 deletions(-) diff --git a/app/model/db/notification.py b/app/model/db/notification.py index 3d19f4cec..98f0ae81f 100644 --- a/app/model/db/notification.py +++ b/app/model/db/notification.py @@ -203,6 +203,8 @@ class NotificationType(StrEnum): CANCEL_TRANSFER = "CancelTransfer" FORCE_LOCK = "ForceLock" FORCE_UNLOCK = "ForceUnlock" + CHANGE_TO_REDEEMED = "ChangeToRedeemed" + CHANGE_TO_CANCELED = "ChangeToCanceled" class NotificationBlockNumber(Base): diff --git a/batch/processor_Notifications_Token.py b/batch/processor_Notifications_Token.py index 1153b9599..1d3f74ad1 100644 --- a/batch/processor_Notifications_Token.py +++ b/batch/processor_Notifications_Token.py @@ -45,6 +45,7 @@ NotificationBlockNumber, NotificationType, ) +from app.model.schema.base import TokenType from app.utils.asyncio_utils import SemaphoreTaskGroup from app.utils.company_list import CompanyList from app.utils.web3_utils import AsyncWeb3Wrapper @@ -70,10 +71,21 @@ class Watcher: contract_cache: dict[str, Web3AsyncContract] = {} - def __init__(self, filter_name: str, filter_params: dict, notification_type: str): + def __init__( + self, + filter_name: str, + filter_params: dict, + notification_type: str, + token_type_list: list[TokenType] = None, + skip_past_data_on_initial_sync: bool = False, + ): + if token_type_list is None: + token_type_list = list([]) self.filter_name = filter_name self.filter_params = filter_params self.notification_type = notification_type + self.token_type_list = token_type_list + self.skip_past_data_on_initial_sync = skip_past_data_on_initial_sync @staticmethod def _gen_notification_id(entry, option_type=0): @@ -91,15 +103,19 @@ async def _gen_block_timestamp(entry): ).replace(tzinfo=None) @staticmethod - async def _get_token_all_list(db_session: AsyncSession): + async def _get_token_all_list( + db_session: AsyncSession, token_type_list: list[TokenType] + ): _tokens = [] + + stmt = select(IDXTokenListRegister).join( + Listing, + and_(Listing.token_address == IDXTokenListRegister.token_address), + ) + if len(token_type_list) != 0: + stmt = stmt.where(IDXTokenListRegister.token_template.in_(token_type_list)) registered_tokens: Sequence[IDXTokenListRegister] = ( - await db_session.scalars( - select(IDXTokenListRegister).join( - Listing, - and_(Listing.token_address == IDXTokenListRegister.token_address), - ) - ) + await db_session.scalars(stmt) ).all() for registered_token in registered_tokens: _tokens.append( @@ -126,7 +142,9 @@ async def loop(self): try: # Get listed tokens - _token_list = await self._get_token_all_list(db_session) + _token_list = await self._get_token_all_list( + db_session, self.token_type_list + ) latest_block_number = await async_web3.eth.block_number for _token in _token_list: @@ -136,6 +154,8 @@ async def loop(self): db_session=db_session, contract_address=_token["token"].token_address, notification_type=self.notification_type, + latest_block_number=latest_block_number, + skip_past_data_on_initial_sync=self.skip_past_data_on_initial_sync, ) + 1 ) @@ -210,7 +230,11 @@ async def loop(self): @staticmethod async def __get_synchronized_block_number( - db_session: AsyncSession, contract_address: str, notification_type: str + db_session: AsyncSession, + contract_address: str, + notification_type: str, + latest_block_number: int, + skip_past_data_on_initial_sync: bool, ): """Get latest synchronized blockNumber""" notification_block_number: NotificationBlockNumber | None = ( @@ -222,6 +246,8 @@ async def __get_synchronized_block_number( ) ).first() if notification_block_number is None: + if skip_past_data_on_initial_sync is True: + return latest_block_number - 1 return -1 else: return notification_block_number.latest_block_number @@ -515,6 +541,100 @@ async def db_merge( await db_session.merge(notification) +class WatchChangeToRedeemed(Watcher): + """Watch ChangeToRedeemed Event for Bond Token + + - Process for registering a notification when a token status is changed to redeemed. + """ + + def __init__(self): + super().__init__( + filter_name="ChangeToRedeemed", + filter_params={}, + notification_type=NotificationType.CHANGE_TO_REDEEMED, + token_type_list=[TokenType.IbetStraightBond], + skip_past_data_on_initial_sync=True, + ) + + async def db_merge( + self, + db_session: AsyncSession, + token_contract: Web3AsyncContract, + token_type: str, + log_entries: list[EventData], + token_owner_address: str, + ): + company_list = await CompanyList.get() + token_name = await AsyncContract.call_function( + contract=token_contract, function_name="name", args=(), default_returns="" + ) + for entry in log_entries: + company = company_list.find(token_owner_address) + metadata = { + "company_name": company.corporate_name, + "token_address": entry["address"], + "token_name": token_name, + "exchange_address": "", + "token_type": token_type, + } + notification = Notification() + notification.notification_id = self._gen_notification_id(entry) + notification.notification_type = self.notification_type + notification.priority = 0 + notification.address = None + notification.block_timestamp = await self._gen_block_timestamp(entry) + notification.args = dict(entry["args"]) + notification.metainfo = metadata + await db_session.merge(notification) + + +class WatchChangeToCanceled(Watcher): + """Watch ChangeToCanceled Event for Share Token + + - Process for registering a notification when a token status is changed to canceled. + """ + + def __init__(self): + super().__init__( + filter_name="ChangeToCanceled", + filter_params={}, + notification_type=NotificationType.CHANGE_TO_CANCELED, + token_type_list=[TokenType.IbetShare], + skip_past_data_on_initial_sync=True, + ) + + async def db_merge( + self, + db_session: AsyncSession, + token_contract: Web3AsyncContract, + token_type: str, + log_entries: list[EventData], + token_owner_address: str, + ): + company_list = await CompanyList.get() + token_name = await AsyncContract.call_function( + contract=token_contract, function_name="name", args=(), default_returns="" + ) + for entry in log_entries: + company = company_list.find(token_owner_address) + metadata = { + "company_name": company.corporate_name, + "token_address": entry["address"], + "token_name": token_name, + "exchange_address": "", + "token_type": token_type, + } + notification = Notification() + notification.notification_id = self._gen_notification_id(entry) + notification.notification_type = self.notification_type + notification.priority = 0 + notification.address = None + notification.block_timestamp = await self._gen_block_timestamp(entry) + notification.args = dict(entry["args"]) + notification.metainfo = metadata + await db_session.merge(notification) + + # メイン処理 async def main(): watchers = [ @@ -524,6 +644,8 @@ async def main(): WatchCancelTransfer(), WatchForceLock(), WatchForceUnlock(), + WatchChangeToRedeemed(), + WatchChangeToCanceled(), ] LOG.info("Service started successfully") diff --git a/tests/app/notification_Notifications_GET_test.py b/tests/app/notification_Notifications_GET_test.py index c3281ab6c..c27d966ba 100644 --- a/tests/app/notification_Notifications_GET_test.py +++ b/tests/app/notification_Notifications_GET_test.py @@ -544,10 +544,10 @@ def test_error_1(self, client: TestClient, session: Session): { "type": "enum", "loc": ["query", "notification_type"], - "msg": "Input should be 'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock' or 'ForceUnlock'", + "msg": "Input should be 'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock', 'ForceUnlock', 'ChangeToRedeemed' or 'ChangeToCanceled'", "input": "hoge", "ctx": { - "expected": "'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock' or 'ForceUnlock'" + "expected": "'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock', 'ForceUnlock', 'ChangeToRedeemed' or 'ChangeToCanceled'" }, }, { diff --git a/tests/batch/processor_Notifications_Token_test.py b/tests/batch/processor_Notifications_Token_test.py index daab5aab3..6293133dc 100644 --- a/tests/batch/processor_Notifications_Token_test.py +++ b/tests/batch/processor_Notifications_Token_test.py @@ -42,16 +42,20 @@ from tests.account_config import eth_account from tests.conftest import DeployedContract, SharedContract, UnitTestAccount from tests.contract_modules import ( + bond_change_to_redeemed, coupon_register_list, coupon_transfer_to_exchange, coupon_withdraw_from_exchange, + issue_bond_token, issue_coupon_token, issue_share_token, + register_bond_list, register_personalinfo, register_share_list, share_apply_for_transfer, share_approve_transfer, share_cancel_transfer, + share_change_to_canceled, share_force_lock, share_force_unlock, share_set_transfer_approval_required, @@ -121,6 +125,64 @@ async def prepare_coupon_token( return token +async def prepare_bond_token( + issuer: UnitTestAccount, + exchange: DeployedContract, + token_list: DeployedContract, + personal_info: DeployedContract, + async_session: AsyncSession, +): + # Issue token + args = { + "name": "テスト債券", + "symbol": "BOND", + "totalSupply": 1000000, + "tradableExchange": exchange["address"], + "faceValue": 10000, + "interestRate": 602, + "interestPaymentDate1": "0101", + "interestPaymentDate2": "0201", + "interestPaymentDate3": "0301", + "interestPaymentDate4": "0401", + "interestPaymentDate5": "0501", + "interestPaymentDate6": "0601", + "interestPaymentDate7": "0701", + "interestPaymentDate8": "0801", + "interestPaymentDate9": "0901", + "interestPaymentDate10": "1001", + "interestPaymentDate11": "1101", + "interestPaymentDate12": "1201", + "redemptionDate": "20191231", + "redemptionValue": 10000, + "returnDate": "20191231", + "returnAmount": "商品券をプレゼント", + "purpose": "新商品の開発資金として利用。", + "memo": "メモ", + "contactInformation": "問い合わせ先", + "privacyPolicy": "プライバシーポリシー", + "personalInfoAddress": personal_info["address"], + "transferable": True, + "isRedeemed": False, + "faceValueCurrency": "JPY", + "interestPaymentCurrency": "JPY", + "redemptionValueCurrency": "JPY", + "baseFxRate": "", + } + token = issue_bond_token(issuer, args) + register_bond_list(issuer, token, token_list) + + _listing = Listing() + _listing.token_address = token["address"] + _listing.is_public = True + _listing.max_holding_quantity = 1000000 + _listing.max_sell_amount = 1000000 + _listing.owner_address = issuer["account_address"] + async_session.add(_listing) + await async_session.commit() + + return token + + async def prepare_share_token( issuer: UnitTestAccount, exchange: DeployedContract, @@ -2241,3 +2303,793 @@ async def test_error_1( await async_session.scalars(select(NotificationBlockNumber).limit(1)) ).first() assert _notification_block_number is None + + +@pytest.mark.asyncio +class TestWatchChangeToRedeemed: + issuer = eth_account["issuer"] + lock_account = eth_account["user1"] + + ########################################################################### + # Normal Case + ########################################################################### + + # + # Initial Sync + async def test_normal_1( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToRedeemed") + exchange_contract = shared_contract["IbetStraightBondExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_bond_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetStraightBond" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + _notification = ( + await async_session.scalars( + select(Notification) + .where( + Notification.notification_id + == "0x{:012x}{:06x}{:06x}{:02x}".format(block_number, 0, 0, 0) + ) + .limit(1) + ) + ).first() + assert _notification is None + + _notification_block_number: NotificationBlockNumber = ( + await async_session.scalars( + select(NotificationBlockNumber) + .where( + and_( + NotificationBlockNumber.notification_type + == NotificationType.CHANGE_TO_REDEEMED, + NotificationBlockNumber.contract_address == token["address"], + ) + ) + .limit(1) + ) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # Single event logs + async def test_normal_2( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToRedeemed") + exchange_contract = shared_contract["IbetStraightBondExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_bond_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetStraightBond" + async_session.add(idx_token_list_item) + + notification_block_number = NotificationBlockNumber() + notification_block_number.notification_type = ( + NotificationType.CHANGE_TO_REDEEMED + ) + notification_block_number.contract_address = token["address"] + notification_block_number.latest_block_number = web3.eth.block_number + async_session.add(notification_block_number) + await async_session.commit() + + # Emit ChangeToRedeemed event + bond_change_to_redeemed( + invoker=self.issuer, + token=token, + ) + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + async_session.expunge_all() + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 1 + assert ( + _notification_list[0].notification_type + == NotificationType.CHANGE_TO_REDEEMED + ) + assert _notification_list[0].priority == 0 + assert _notification_list[0].address is None + assert _notification_list[0].args == {} + assert _notification_list[0].metainfo == { + "company_name": "株式会社DEMO", + "exchange_address": "", + "token_address": token["address"], + "token_name": "テスト債券", + "token_type": "IbetStraightBond", + } + + _notification_block_number: NotificationBlockNumber = ( + await async_session.scalars( + select(NotificationBlockNumber) + .where( + and_( + NotificationBlockNumber.notification_type + == NotificationType.CHANGE_TO_REDEEMED, + NotificationBlockNumber.contract_address == token["address"], + ) + ) + .limit(1) + ) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # Multi event logs + async def test_normal_3( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToRedeemed") + exchange_contract = shared_contract["IbetStraightBondExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_bond_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetStraightBond" + async_session.add(idx_token_list_item) + + notification_block_number = NotificationBlockNumber() + notification_block_number.notification_type = ( + NotificationType.CHANGE_TO_REDEEMED + ) + notification_block_number.contract_address = token["address"] + notification_block_number.latest_block_number = web3.eth.block_number + async_session.add(notification_block_number) + await async_session.commit() + + # Emit ChangeToRedeemed event + bond_change_to_redeemed( + invoker=self.issuer, + token=token, + ) + bond_change_to_redeemed( + invoker=self.issuer, + token=token, + ) + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + async_session.expunge_all() + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 2 + assert ( + _notification_list[0].notification_type + == NotificationType.CHANGE_TO_REDEEMED + ) + assert _notification_list[0].priority == 0 + assert _notification_list[0].address is None + assert _notification_list[0].args == {} + assert _notification_list[0].metainfo == { + "company_name": "株式会社DEMO", + "exchange_address": "", + "token_address": token["address"], + "token_name": "テスト債券", + "token_type": "IbetStraightBond", + } + assert ( + _notification_list[1].notification_type + == NotificationType.CHANGE_TO_REDEEMED + ) + assert _notification_list[1].priority == 0 + assert _notification_list[1].address is None + assert _notification_list[1].args == {} + assert _notification_list[1].metainfo == { + "company_name": "株式会社DEMO", + "exchange_address": "", + "token_address": token["address"], + "token_name": "テスト債券", + "token_type": "IbetStraightBond", + } + + _notification_block_number: NotificationBlockNumber = ( + await async_session.scalars( + select(NotificationBlockNumber) + .where( + and_( + NotificationBlockNumber.notification_type + == NotificationType.CHANGE_TO_REDEEMED, + NotificationBlockNumber.contract_address == token["address"], + ) + ) + .limit(1) + ) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # No event logs + async def test_normal_4( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToRedeemed") + exchange_contract = shared_contract["IbetStraightBondExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_bond_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetStraightBond" + async_session.add(idx_token_list_item) + + notification_block_number = NotificationBlockNumber() + notification_block_number.notification_type = ( + NotificationType.CHANGE_TO_REDEEMED + ) + notification_block_number.contract_address = token["address"] + notification_block_number.latest_block_number = web3.eth.block_number + async_session.add(notification_block_number) + await async_session.commit() + + # Not emit ChangeToRedeemed event + pass + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 0 + + _notification_block_number = ( + await async_session.scalars(select(NotificationBlockNumber).limit(1)) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # Skip past data on initial sync + async def test_normal_5( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToRedeemed") + exchange_contract = shared_contract["IbetStraightBondExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_bond_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetStraightBond" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Emit ChangeToRedeemed event + bond_change_to_redeemed( + invoker=self.issuer, + token=token, + ) + web3.provider.make_request(RPCEndpoint("evm_mine"), []) + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 0 + + _notification_block_number = ( + await async_session.scalars(select(NotificationBlockNumber).limit(1)) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # ########################################################################### + # # Error Case + # ########################################################################### + + # + # Error occur + @mock.patch( + "web3.eth.async_eth.AsyncEth.get_logs", + MagicMock(side_effect=Exception()), + ) + async def test_error_1( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToRedeemed") + exchange_contract = shared_contract["IbetStraightBondExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_bond_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetStraightBond" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + _notification = ( + await async_session.scalars( + select(Notification).order_by(Notification.created).limit(1) + ) + ).first() + assert _notification is None + + _notification_block_number = ( + await async_session.scalars(select(NotificationBlockNumber).limit(1)) + ).first() + assert _notification_block_number is None + + +@pytest.mark.asyncio +class TestWatchChangeToCanceled: + issuer = eth_account["issuer"] + lock_account = eth_account["user1"] + + ########################################################################### + # Normal Case + ########################################################################### + + # + # Initial Sync + async def test_normal_1( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToCanceled") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + _notification = ( + await async_session.scalars( + select(Notification) + .where( + Notification.notification_id + == "0x{:012x}{:06x}{:06x}{:02x}".format(block_number, 0, 0, 0) + ) + .limit(1) + ) + ).first() + assert _notification is None + + _notification_block_number: NotificationBlockNumber = ( + await async_session.scalars( + select(NotificationBlockNumber) + .where( + and_( + NotificationBlockNumber.notification_type + == NotificationType.CHANGE_TO_CANCELED, + NotificationBlockNumber.contract_address == token["address"], + ) + ) + .limit(1) + ) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # Single event logs + async def test_normal_2( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToCanceled") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + + notification_block_number = NotificationBlockNumber() + notification_block_number.notification_type = ( + NotificationType.CHANGE_TO_CANCELED + ) + notification_block_number.contract_address = token["address"] + notification_block_number.latest_block_number = web3.eth.block_number + async_session.add(notification_block_number) + await async_session.commit() + + # Emit ChangeToCanceled event + share_change_to_canceled( + invoker=self.issuer, + token=token, + ) + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + async_session.expunge_all() + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 1 + assert ( + _notification_list[0].notification_type + == NotificationType.CHANGE_TO_CANCELED + ) + assert _notification_list[0].priority == 0 + assert _notification_list[0].address is None + assert _notification_list[0].args == {} + assert _notification_list[0].metainfo == { + "company_name": "株式会社DEMO", + "exchange_address": "", + "token_address": token["address"], + "token_name": "テスト株式", + "token_type": "IbetShare", + } + + _notification_block_number: NotificationBlockNumber = ( + await async_session.scalars( + select(NotificationBlockNumber) + .where( + and_( + NotificationBlockNumber.notification_type + == NotificationType.CHANGE_TO_CANCELED, + NotificationBlockNumber.contract_address == token["address"], + ) + ) + .limit(1) + ) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # Multi event logs + async def test_normal_3( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToCanceled") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + + notification_block_number = NotificationBlockNumber() + notification_block_number.notification_type = ( + NotificationType.CHANGE_TO_CANCELED + ) + notification_block_number.contract_address = token["address"] + notification_block_number.latest_block_number = web3.eth.block_number + async_session.add(notification_block_number) + await async_session.commit() + + # Emit ChangeToCanceled event + share_change_to_canceled( + invoker=self.issuer, + token=token, + ) + share_change_to_canceled( + invoker=self.issuer, + token=token, + ) + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + async_session.expunge_all() + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 2 + assert ( + _notification_list[0].notification_type + == NotificationType.CHANGE_TO_CANCELED + ) + assert _notification_list[0].priority == 0 + assert _notification_list[0].address is None + assert _notification_list[0].args == {} + assert _notification_list[0].metainfo == { + "company_name": "株式会社DEMO", + "exchange_address": "", + "token_address": token["address"], + "token_name": "テスト株式", + "token_type": "IbetShare", + } + assert ( + _notification_list[1].notification_type + == NotificationType.CHANGE_TO_CANCELED + ) + assert _notification_list[1].priority == 0 + assert _notification_list[1].address is None + assert _notification_list[1].args == {} + assert _notification_list[1].metainfo == { + "company_name": "株式会社DEMO", + "exchange_address": "", + "token_address": token["address"], + "token_name": "テスト株式", + "token_type": "IbetShare", + } + + _notification_block_number: NotificationBlockNumber = ( + await async_session.scalars( + select(NotificationBlockNumber) + .where( + and_( + NotificationBlockNumber.notification_type + == NotificationType.CHANGE_TO_CANCELED, + NotificationBlockNumber.contract_address == token["address"], + ) + ) + .limit(1) + ) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # No event logs + async def test_normal_4( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToCanceled") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + + notification_block_number = NotificationBlockNumber() + notification_block_number.notification_type = ( + NotificationType.CHANGE_TO_CANCELED + ) + notification_block_number.contract_address = token["address"] + notification_block_number.latest_block_number = web3.eth.block_number + async_session.add(notification_block_number) + await async_session.commit() + + # Not emit ChangeToCanceled event + pass + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 0 + + _notification_block_number = ( + await async_session.scalars(select(NotificationBlockNumber).limit(1)) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # + # Skip past data on initial sync + async def test_normal_5( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToCanceled") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Emit ChangeToCanceled event + share_change_to_canceled( + invoker=self.issuer, + token=token, + ) + web3.provider.make_request(RPCEndpoint("evm_mine"), []) + + # Run target process + await watcher.loop() + + # Assertion + block_number = web3.eth.block_number + + _notification_list = (await async_session.scalars(select(Notification))).all() + assert len(_notification_list) == 0 + + _notification_block_number = ( + await async_session.scalars(select(NotificationBlockNumber).limit(1)) + ).first() + assert _notification_block_number.latest_block_number == block_number + + # ########################################################################### + # # Error Case + # ########################################################################### + + # + # Error occur + @mock.patch( + "web3.eth.async_eth.AsyncEth.get_logs", + MagicMock(side_effect=Exception()), + ) + async def test_error_1( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchChangeToCanceled") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + _notification = ( + await async_session.scalars( + select(Notification).order_by(Notification.created).limit(1) + ) + ).first() + assert _notification is None + + _notification_block_number = ( + await async_session.scalars(select(NotificationBlockNumber).limit(1)) + ).first() + assert _notification_block_number is None diff --git a/tests/contract_modules.py b/tests/contract_modules.py index 309b38558..ab2899b42 100644 --- a/tests/contract_modules.py +++ b/tests/contract_modules.py @@ -381,6 +381,15 @@ def bond_set_tradable_exchange(invoker, token, exchange_address: str): ) +# BONDトークン:償還状態に変更 +def bond_change_to_redeemed(invoker, token): + web3.eth.default_account = invoker["account_address"] + TokenContract = Contract.get_contract("IbetStraightBond", token["address"]) + TokenContract.functions.changeToRedeemed().transact( + {"from": invoker["account_address"]} + ) + + ############################################################### # Share Token ############################################################### @@ -665,6 +674,15 @@ def share_set_tradable_exchange(invoker, token, exchange_address: str): ) +# SHAREトークン:消却状態に変更 +def share_change_to_canceled(invoker, token): + web3.eth.default_account = invoker["account_address"] + TokenContract = Contract.get_contract("IbetShare", token["address"]) + TokenContract.functions.changeToCanceled().transact( + {"from": invoker["account_address"]} + ) + + ############################################################### # Coupon Token ############################################################### From e0f9f42704a2b6a2e72d3f1521c891a20e443f55 Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Thu, 31 Jul 2025 17:19:20 +0900 Subject: [PATCH 08/22] Fix balance indexing (#1664) * Refactor position indexing * Add ChangeToRedeemed and ChangeToCanceled notification types --- batch/indexer_Position_Bond.py | 2 +- batch/indexer_Position_Coupon.py | 2 +- batch/indexer_Position_Membership.py | 2 +- batch/indexer_Position_Share.py | 2 +- docs/ibet_wallet_api.yaml | 2 + tests/batch/indexer_Position_Bond_test.py | 231 +++++++++-------- tests/batch/indexer_Position_Coupon_test.py | 25 +- .../batch/indexer_Position_Membership_test.py | 26 +- tests/batch/indexer_Position_Share_test.py | 234 ++++++++++-------- 9 files changed, 282 insertions(+), 244 deletions(-) diff --git a/batch/indexer_Position_Bond.py b/batch/indexer_Position_Bond.py index 46f9828c0..63478b54a 100644 --- a/batch/indexer_Position_Bond.py +++ b/batch/indexer_Position_Bond.py @@ -1857,7 +1857,7 @@ async def __sink_on_position( position.exchange_commitment = exchange_commitment await db_session.merge(position) elif any( - value is not None and value > 0 + value is not None for value in [ balance, pending_transfer, diff --git a/batch/indexer_Position_Coupon.py b/batch/indexer_Position_Coupon.py index b4b6b0521..36240d07f 100644 --- a/batch/indexer_Position_Coupon.py +++ b/batch/indexer_Position_Coupon.py @@ -944,7 +944,7 @@ async def __sink_on_position( position.exchange_commitment = exchange_commitment await db_session.merge(position) elif any( - value is not None and value > 0 + value is not None for value in [balance, exchange_balance, exchange_commitment] ): LOG.debug( diff --git a/batch/indexer_Position_Membership.py b/batch/indexer_Position_Membership.py index 1213ce319..f72304130 100644 --- a/batch/indexer_Position_Membership.py +++ b/batch/indexer_Position_Membership.py @@ -908,7 +908,7 @@ async def __sink_on_position( position.exchange_commitment = exchange_commitment await db_session.merge(position) elif any( - value is not None and value > 0 + value is not None for value in [balance, exchange_balance, exchange_commitment] ): LOG.debug( diff --git a/batch/indexer_Position_Share.py b/batch/indexer_Position_Share.py index a011c6160..4cb838ee3 100644 --- a/batch/indexer_Position_Share.py +++ b/batch/indexer_Position_Share.py @@ -1857,7 +1857,7 @@ async def __sink_on_position( position.exchange_commitment = exchange_commitment await db_session.merge(position) elif any( - value is not None and value > 0 + value is not None for value in [ balance, pending_transfer, diff --git a/docs/ibet_wallet_api.yaml b/docs/ibet_wallet_api.yaml index b1b940ca7..ab04a8c69 100644 --- a/docs/ibet_wallet_api.yaml +++ b/docs/ibet_wallet_api.yaml @@ -9416,6 +9416,8 @@ components: - CancelTransfer - ForceLock - ForceUnlock + - ChangeToRedeemed + - ChangeToCanceled title: NotificationType NotificationUpdateResponse: properties: diff --git a/tests/batch/indexer_Position_Bond_test.py b/tests/batch/indexer_Position_Bond_test.py index b7ba56312..fa31ff68c 100644 --- a/tests/batch/indexer_Position_Bond_test.py +++ b/tests/batch/indexer_Position_Bond_test.py @@ -637,10 +637,29 @@ async def test_normal_4_2(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 3000 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 0 + assert _position_issuer.exchange_commitment == 0 + + _position_trader: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.trader["account_address"]) + .limit(1) + ).first() + assert _position_trader.token_address == token["address"] + assert _position_trader.account_address == self.trader["account_address"] + assert _position_trader.balance == 0 + assert _position_trader.pending_transfer == 0 + assert _position_trader.exchange_balance == 0 + assert _position_trader.exchange_commitment == 0 _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( select(IDXPositionBondBlockNumber) @@ -649,14 +668,6 @@ async def test_normal_4_2(self, processor, shared_contract, session): ).first() assert _idx_position_bond_block_number.latest_block_number == block_number - _position = _position_list[0] - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 3000 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 0 - assert _position.exchange_commitment == 0 - _locked_list: Sequence[IDXLockedPosition] = session.scalars( select(IDXLockedPosition).order_by(IDXLockedPosition.created) ).all() @@ -934,8 +945,6 @@ async def test_normal_5_2(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer - # - Lock # - ForceChangeLockedAccount async def test_normal_5_3(self, processor, shared_contract, session): # Issue Token @@ -983,34 +992,63 @@ async def test_normal_5_3(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 - - _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( - select(IDXPositionBondBlockNumber) - .where(IDXPositionBondBlockNumber.token_address == token["address"]) + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where( + and_( + IDXPosition.token_address == token["address"], + IDXPosition.account_address == self.issuer["account_address"], + ) + ) .limit(1) ).first() - assert _idx_position_bond_block_number.latest_block_number == block_number - - _position: IDXPosition = session.scalars( + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 3000 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 0 + assert _position_issuer.exchange_commitment == 0 + + _position_trader_1: IDXPosition = session.scalars( select(IDXPosition) .where( and_( IDXPosition.token_address == token["address"], - IDXPosition.account_address == self.issuer["account_address"], + IDXPosition.account_address == self.trader["account_address"], ) ) .limit(1) ).first() - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 3000 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 0 - assert _position.exchange_commitment == 0 + assert _position_trader_1.token_address == token["address"] + assert _position_trader_1.account_address == self.trader["account_address"] + assert _position_trader_1.balance == 0 + assert _position_trader_1.pending_transfer == 0 + assert _position_trader_1.exchange_balance == 0 + assert _position_trader_1.exchange_commitment == 0 + + _position_trader_2: IDXPosition = session.scalars( + select(IDXPosition) + .where( + and_( + IDXPosition.token_address == token["address"], + IDXPosition.account_address == self.trader2["account_address"], + ) + ) + .limit(1) + ).first() + assert _position_trader_2.token_address == token["address"] + assert _position_trader_2.account_address == self.trader2["account_address"] + assert _position_trader_2.balance == 0 + assert _position_trader_2.pending_transfer == 0 + assert _position_trader_2.exchange_balance == 0 + assert _position_trader_2.exchange_commitment == 0 + + _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( + select(IDXPositionBondBlockNumber) + .where(IDXPositionBondBlockNumber.token_address == token["address"]) + .limit(1) + ).first() + assert _idx_position_bond_block_number.latest_block_number == block_number _locked_list: Sequence[IDXLockedPosition] = session.scalars( select(IDXLockedPosition).order_by(IDXLockedPosition.created) @@ -1159,7 +1197,6 @@ async def test_normal_7(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer # - Apply For Transfer async def test_normal_8(self, processor, shared_contract, session): # Issue Token @@ -1253,9 +1290,7 @@ async def test_normal_8(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer - # - Apply For Transfer - # - Approve + # - Approve Transfer async def test_normal_9(self, processor, shared_contract, session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -1370,9 +1405,7 @@ async def test_normal_9(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer - # - Apply For Transfer - # - Cancel + # - Cancel Transfer async def test_normal_10(self, processor, shared_contract, session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -1608,17 +1641,17 @@ async def test_normal_12(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 - _position: IDXPosition = _position_list[0] - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 111 + 222 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 111 - 222 - 333 - assert _position.exchange_commitment == 333 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + 111 + 222 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 111 - 222 - 333 + assert _position_issuer.exchange_commitment == 333 _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( select(IDXPositionBondBlockNumber) @@ -1679,17 +1712,17 @@ async def test_normal_13(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 - _position: IDXPosition = _position_list[0] - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 55 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 55 - 66 - assert _position.exchange_commitment == 66 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + 55 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 55 - 66 + assert _position_issuer.exchange_commitment == 66 _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( select(IDXPositionBondBlockNumber) @@ -1770,19 +1803,7 @@ async def test_normal_14(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 2 - - _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( - select(IDXPositionBondBlockNumber) - .where(IDXPositionBondBlockNumber.token_address == token["address"]) - .limit(1) - ).first() - assert _idx_position_bond_block_number.latest_block_number == block_number - - _position: IDXPosition = session.scalars( + _position_issuer: IDXPosition = session.scalars( select(IDXPosition) .where( and_( @@ -1792,14 +1813,14 @@ async def test_normal_14(self, processor, shared_contract, session): ) .limit(1) ).first() - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 200 - assert _position.exchange_commitment == 0 - - _position: IDXPosition = session.scalars( + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 200 + assert _position_issuer.exchange_commitment == 0 + + _position_trader: IDXPosition = session.scalars( select(IDXPosition) .where( and_( @@ -1809,12 +1830,19 @@ async def test_normal_14(self, processor, shared_contract, session): ) .limit(1) ).first() - assert _position.token_address == token["address"] - assert _position.account_address == self.trader["account_address"] - assert _position.balance == 0 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 200 - assert _position.exchange_commitment == 0 + assert _position_trader.token_address == token["address"] + assert _position_trader.account_address == self.trader["account_address"] + assert _position_trader.balance == 0 + assert _position_trader.pending_transfer == 0 + assert _position_trader.exchange_balance == 200 + assert _position_trader.exchange_commitment == 0 + + _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( + select(IDXPositionBondBlockNumber) + .where(IDXPositionBondBlockNumber.token_address == token["address"]) + .limit(1) + ).first() + assert _idx_position_bond_block_number.latest_block_number == block_number # # No event logs @@ -2113,17 +2141,17 @@ async def test_normal_19(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 - _position: IDXPosition = _position_list[0] - assert _position.token_address == token1["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 55 - 100 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 55 - 66 - assert _position.exchange_commitment == 66 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token1["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + 55 - 100 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 55 - 66 + assert _position_issuer.exchange_commitment == 66 _idx_position_bond_block_number: IDXPositionBondBlockNumber = session.scalars( select(IDXPositionBondBlockNumber) @@ -2142,11 +2170,6 @@ async def test_normal_19(self, processor, shared_contract, session): session.rollback() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 2 - _idx_position_bond_block_number1: IDXPositionBondBlockNumber = session.scalars( select(IDXPositionBondBlockNumber) .where(IDXPositionBondBlockNumber.token_address == token1["address"]) diff --git a/tests/batch/indexer_Position_Coupon_test.py b/tests/batch/indexer_Position_Coupon_test.py index 3f97909ec..f94d6518b 100644 --- a/tests/batch/indexer_Position_Coupon_test.py +++ b/tests/batch/indexer_Position_Coupon_test.py @@ -898,10 +898,16 @@ async def test_normal_11(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position.token_address == token1["address"] + assert _position.account_address == self.issuer["account_address"] + assert _position.balance == 1000000 - 10000 + 55 - 100 + assert _position.exchange_balance == 10000 - 55 - 66 + assert _position.exchange_commitment == 66 _idx_position_coupon_block_number: IDXPositionCouponBlockNumber = ( session.scalars( @@ -912,13 +918,6 @@ async def test_normal_11(self, processor, shared_contract, session): ) assert _idx_position_coupon_block_number.latest_block_number == block_number1 - _position: IDXPosition = _position_list[0] - assert _position.token_address == token1["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 55 - 100 - assert _position.exchange_balance == 10000 - 55 - 66 - assert _position.exchange_commitment == 66 - # Token2 Listing self.listing_token(token2["address"], session) @@ -932,10 +931,6 @@ async def test_normal_11(self, processor, shared_contract, session): session.rollback() assert eth_getCode_mock.call_count == 0 - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 2 _idx_position_coupon_block_number1: IDXPositionCouponBlockNumber = ( session.scalars( diff --git a/tests/batch/indexer_Position_Membership_test.py b/tests/batch/indexer_Position_Membership_test.py index fe41f2b23..c1b3e540e 100644 --- a/tests/batch/indexer_Position_Membership_test.py +++ b/tests/batch/indexer_Position_Membership_test.py @@ -852,10 +852,16 @@ async def test_normal_10(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position.token_address == token1["address"] + assert _position.account_address == self.issuer["account_address"] + assert _position.balance == 1000000 - 10000 + 55 + assert _position.exchange_balance == 10000 - 55 - 66 + assert _position.exchange_commitment == 66 _idx_position_coupon_block_number: IDXPositionMembershipBlockNumber = ( session.scalars( @@ -868,13 +874,6 @@ async def test_normal_10(self, processor, shared_contract, session): ) assert _idx_position_coupon_block_number.latest_block_number == block_number1 - _position: IDXPosition = _position_list[0] - assert _position.token_address == token1["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 55 - assert _position.exchange_balance == 10000 - 55 - 66 - assert _position.exchange_commitment == 66 - # Token2 Listing self.listing_token(token2["address"], session) @@ -883,11 +882,8 @@ async def test_normal_10(self, processor, shared_contract, session): await processor.sync_new_logs() session.rollback() - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 2 + # Assertion _idx_position_coupon_block_number1: IDXPositionMembershipBlockNumber = ( session.scalars( select(IDXPositionMembershipBlockNumber) diff --git a/tests/batch/indexer_Position_Share_test.py b/tests/batch/indexer_Position_Share_test.py index f18d0df4e..9a0b1b037 100644 --- a/tests/batch/indexer_Position_Share_test.py +++ b/tests/batch/indexer_Position_Share_test.py @@ -616,10 +616,29 @@ async def test_normal_4_2(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 3000 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 0 + assert _position_issuer.exchange_commitment == 0 + + _position_trader: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.trader["account_address"]) + .limit(1) + ).first() + assert _position_trader.token_address == token["address"] + assert _position_trader.account_address == self.trader["account_address"] + assert _position_trader.balance == 0 + assert _position_trader.pending_transfer == 0 + assert _position_trader.exchange_balance == 0 + assert _position_trader.exchange_commitment == 0 _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( select(IDXPositionShareBlockNumber) @@ -628,14 +647,6 @@ async def test_normal_4_2(self, processor, shared_contract, session): ).first() assert _idx_position_share_block_number.latest_block_number == block_number - _position = _position_list[0] - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 3000 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 0 - assert _position.exchange_commitment == 0 - _locked_list: Sequence[IDXLockedPosition] = session.scalars( select(IDXLockedPosition).order_by(IDXLockedPosition.created) ).all() @@ -905,8 +916,6 @@ async def test_normal_5_2(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer - # - Lock # - ForceChangeLockedAccount async def test_normal_5_3(self, processor, shared_contract, session): # Issue Token @@ -953,34 +962,63 @@ async def test_normal_5_3(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 - - _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( - select(IDXPositionShareBlockNumber) - .where(IDXPositionShareBlockNumber.token_address == token["address"]) + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where( + and_( + IDXPosition.token_address == token["address"], + IDXPosition.account_address == self.issuer["account_address"], + ) + ) .limit(1) ).first() - assert _idx_position_share_block_number.latest_block_number == block_number - - _position: IDXPosition = session.scalars( + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 3000 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 0 + assert _position_issuer.exchange_commitment == 0 + + _position_trader_1: IDXPosition = session.scalars( select(IDXPosition) .where( and_( IDXPosition.token_address == token["address"], - IDXPosition.account_address == self.issuer["account_address"], + IDXPosition.account_address == self.trader["account_address"], ) ) .limit(1) ).first() - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 3000 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 0 - assert _position.exchange_commitment == 0 + assert _position_trader_1.token_address == token["address"] + assert _position_trader_1.account_address == self.trader["account_address"] + assert _position_trader_1.balance == 0 + assert _position_trader_1.pending_transfer == 0 + assert _position_trader_1.exchange_balance == 0 + assert _position_trader_1.exchange_commitment == 0 + + _position_trader_2: IDXPosition = session.scalars( + select(IDXPosition) + .where( + and_( + IDXPosition.token_address == token["address"], + IDXPosition.account_address == self.trader2["account_address"], + ) + ) + .limit(1) + ).first() + assert _position_trader_2.token_address == token["address"] + assert _position_trader_2.account_address == self.trader2["account_address"] + assert _position_trader_2.balance == 0 + assert _position_trader_2.pending_transfer == 0 + assert _position_trader_2.exchange_balance == 0 + assert _position_trader_2.exchange_commitment == 0 + + _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( + select(IDXPositionShareBlockNumber) + .where(IDXPositionShareBlockNumber.token_address == token["address"]) + .limit(1) + ).first() + assert _idx_position_share_block_number.latest_block_number == block_number _locked_list = session.scalars( select(IDXLockedPosition).order_by(IDXLockedPosition.created) @@ -1126,7 +1164,6 @@ async def test_normal_7(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer # - Apply For Transfer async def test_normal_8(self, processor, shared_contract, session): # Issue Token @@ -1221,9 +1258,7 @@ async def test_normal_8(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer - # - Apply For Transfer - # - Approve + # - Approve Transfer async def test_normal_9(self, processor, shared_contract, session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -1338,9 +1373,7 @@ async def test_normal_9(self, processor, shared_contract, session): # # Single Token # Single event logs - # - Transfer - # - Apply For Transfer - # - Cancel + # - Cancel Transfer async def test_normal_10(self, processor, shared_contract, session): # Issue Token token_list_contract = shared_contract["TokenList"] @@ -1576,10 +1609,17 @@ async def test_normal_12(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + 111 + 222 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 111 - 222 - 333 + assert _position_issuer.exchange_commitment == 333 _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( select(IDXPositionShareBlockNumber) @@ -1588,14 +1628,6 @@ async def test_normal_12(self, processor, shared_contract, session): ).first() assert _idx_position_share_block_number.latest_block_number == block_number - _position: IDXPosition = _position_list[0] - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 111 + 222 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 111 - 222 - 333 - assert _position.exchange_commitment == 333 - # # Single Token # Multi event with IbetExchange logs @@ -1648,10 +1680,17 @@ async def test_normal_13(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + 55 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 55 - 66 + assert _position_issuer.exchange_commitment == 66 _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( select(IDXPositionShareBlockNumber) @@ -1660,14 +1699,6 @@ async def test_normal_13(self, processor, shared_contract, session): ).first() assert _idx_position_share_block_number.latest_block_number == block_number - _position: IDXPosition = _position_list[0] - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 55 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 55 - 66 - assert _position.exchange_commitment == 66 - # # Single Token # Multi event with DVP logs @@ -1740,19 +1771,7 @@ async def test_normal_14(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 2 - - _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( - select(IDXPositionShareBlockNumber) - .where(IDXPositionShareBlockNumber.token_address == token["address"]) - .limit(1) - ).first() - assert _idx_position_share_block_number.latest_block_number == block_number - - _position: IDXPosition = session.scalars( + _position_issuer: IDXPosition = session.scalars( select(IDXPosition) .where( and_( @@ -1762,14 +1781,14 @@ async def test_normal_14(self, processor, shared_contract, session): ) .limit(1) ).first() - assert _position.token_address == token["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 200 - assert _position.exchange_commitment == 0 - - _position: IDXPosition = session.scalars( + assert _position_issuer.token_address == token["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 200 + assert _position_issuer.exchange_commitment == 0 + + _position_trader: IDXPosition = session.scalars( select(IDXPosition) .where( and_( @@ -1779,12 +1798,19 @@ async def test_normal_14(self, processor, shared_contract, session): ) .limit(1) ).first() - assert _position.token_address == token["address"] - assert _position.account_address == self.trader["account_address"] - assert _position.balance == 0 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 200 - assert _position.exchange_commitment == 0 + assert _position_trader.token_address == token["address"] + assert _position_trader.account_address == self.trader["account_address"] + assert _position_trader.balance == 0 + assert _position_trader.pending_transfer == 0 + assert _position_trader.exchange_balance == 200 + assert _position_trader.exchange_commitment == 0 + + _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( + select(IDXPositionShareBlockNumber) + .where(IDXPositionShareBlockNumber.token_address == token["address"]) + .limit(1) + ).first() + assert _idx_position_share_block_number.latest_block_number == block_number # # No event logs @@ -2084,10 +2110,17 @@ async def test_normal_19(self, processor, shared_contract, session): await processor.sync_new_logs() # Assertion - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 1 + _position_issuer: IDXPosition = session.scalars( + select(IDXPosition) + .where(IDXPosition.account_address == self.issuer["account_address"]) + .limit(1) + ).first() + assert _position_issuer.token_address == token1["address"] + assert _position_issuer.account_address == self.issuer["account_address"] + assert _position_issuer.balance == 1000000 - 10000 + 55 - 100 + assert _position_issuer.pending_transfer == 0 + assert _position_issuer.exchange_balance == 10000 - 55 - 66 + assert _position_issuer.exchange_commitment == 66 _idx_position_share_block_number: IDXPositionShareBlockNumber = session.scalars( select(IDXPositionShareBlockNumber) @@ -2096,14 +2129,6 @@ async def test_normal_19(self, processor, shared_contract, session): ).first() assert _idx_position_share_block_number.latest_block_number == block_number1 - _position: IDXPosition = _position_list[0] - assert _position.token_address == token1["address"] - assert _position.account_address == self.issuer["account_address"] - assert _position.balance == 1000000 - 10000 + 55 - 100 - assert _position.pending_transfer == 0 - assert _position.exchange_balance == 10000 - 55 - 66 - assert _position.exchange_commitment == 66 - # Token2 Listing self.listing_token(token2["address"], session) @@ -2112,11 +2137,8 @@ async def test_normal_19(self, processor, shared_contract, session): await processor.sync_new_logs() session.rollback() - _position_list: Sequence[IDXPosition] = session.scalars( - select(IDXPosition).order_by(IDXPosition.created) - ).all() - assert len(_position_list) == 2 + # Assertion _idx_position_share_block_number1: IDXPositionShareBlockNumber = ( session.scalars( select(IDXPositionShareBlockNumber) From c79b0c3c88ee8834ee25dd961623e4c268de8ac9 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Thu, 31 Jul 2025 18:46:50 +0900 Subject: [PATCH 09/22] Add notification category to Notification model and implement attribute change tracking watcher (#1663) * Add notification category to Notification model and implement attribute change tracking watcher * Add notification category to API schema for enhanced notification filtering * Update tests/batch/processor_Notifications_Token_test.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test to mock ShareToken.get for error handling in processor_Notifications_Token_test.py --------- Co-authored-by: Yoshihito Aso Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/api/routers/notification.py | 3 + app/model/db/__init__.py | 7 +- app/model/db/notification.py | 45 +- app/model/schema/notification.py | 8 +- ...processor_Notifications_Coupon_Exchange.py | 9 + ...essor_Notifications_Membership_Exchange.py | 9 + batch/processor_Notifications_Token.py | 292 ++++++++++++- docs/ibet_wallet_api.yaml | 29 +- .../a5e395bf46a9_v25_9_0_feature_1662.py | 64 +++ .../notification_NotificationsCount_test.py | 5 + ...otification_NotificationsId_DELETE_test.py | 5 + .../notification_NotificationsId_POST_test.py | 5 + .../notification_NotificationsRead_test.py | 5 + .../notification_Notifications_GET_test.py | 23 +- ...ssor_Notifications_Coupon_Exchange_test.py | 27 ++ ..._Notifications_Membership_Exchange_test.py | 27 ++ .../processor_Notifications_Token_test.py | 393 +++++++++++++++++- 17 files changed, 926 insertions(+), 30 deletions(-) create mode 100644 migrations/versions/a5e395bf46a9_v25_9_0_feature_1662.py diff --git a/app/api/routers/notification.py b/app/api/routers/notification.py index 345add283..72af79302 100644 --- a/app/api/routers/notification.py +++ b/app/api/routers/notification.py @@ -61,6 +61,7 @@ async def list_all_notifications( """ Returns notifications filtered by given query. """ + notification_category = request_query.notification_category address = request_query.address notification_type = request_query.notification_type priority = request_query.priority @@ -75,6 +76,8 @@ async def list_all_notifications( ) # Search Filter + if notification_category is not None: + stmt = stmt.where(Notification.notification_category == notification_category) if address is not None: stmt = stmt.where(Notification.address == to_checksum_address(address)) if notification_type is not None: diff --git a/app/model/db/__init__.py b/app/model/db/__init__.py index fdc11c4ea..5a2421441 100644 --- a/app/model/db/__init__.py +++ b/app/model/db/__init__.py @@ -52,7 +52,12 @@ from .listing import Listing from .messaging import ChatWebhook, Mail from .node import Node -from .notification import Notification, NotificationBlockNumber, NotificationType +from .notification import ( + Notification, + NotificationAttributeValue, + NotificationBlockNumber, + NotificationType, +) from .public_info import PublicAccountList, TokenList from .tokenholders import TokenHolder, TokenHolderBatchStatus, TokenHoldersList from .user_info import AccountTag diff --git a/app/model/db/notification.py b/app/model/db/notification.py index 98f0ae81f..b80b5011b 100644 --- a/app/model/db/notification.py +++ b/app/model/db/notification.py @@ -20,6 +20,7 @@ import sys from datetime import datetime from enum import StrEnum +from typing import Literal from sqlalchemy import ( JSON, @@ -63,13 +64,29 @@ class Notification(Base): autoincrement=True, ) + # 通知分類 + notification_category: Mapped[Literal["event_log", "attribute_change"]] = ( + mapped_column( + String(20), + primary_key=True, + ) + ) + # 通知ID - # Spec: 0x | | | | + # Spec: 0x | | | | # ( | は文字列連結 ) - # : blockNumberをhexstringで表現したもの。12桁 - # : transactionIndex(block内でのトランザクションの採番)をhexstringで表現したもの。6桁 - # : logIndex(transaction内でのログの採番)をhexstringで表現したもの。6桁 - # : blockNumber, transactionIndex, logIndexが等しいが、通知としては複数にしたい場合に使用する識別子。2桁(デフォルトは00) + # + # notification_category = "event_log" の場合 + # = : blockNumberをhexstringで表現したもの。12桁 + # = : transactionIndex(block内でのトランザクションの採番)をhexstringで表現したもの。6桁 + # = : logIndex(transaction内でのログの採番)をhexstringで表現したもの。6桁 + # : blockNumber, transactionIndex, logIndexが等しいが、通知としては複数にしたい場合に使用する識別子。2桁(デフォルトは00) + # + # notification_category = "attribute_change" の場合 + # = : ミリ秒タイムスタンプをhexstringで表現したもの。12桁 + # = : コントラクトアドレス。40桁 + # = : 属性名のkeccak256ハッシュ先頭8文字。8桁 + # : 同一のコントラクト・属性で複数通知にする場合の識別子。2桁(デフォルトは00) notification_id: Mapped[str] = mapped_column(String(256), primary_key=True) # 通知タイプ(例:BuySettlementOK, BuyAgreementなど) @@ -122,6 +139,7 @@ def __repr__(self): def json(self): return { + "notification_category": self.notification_category, "notification_type": self.notification_type, "id": self.notification_id, "priority": self.priority, @@ -185,6 +203,7 @@ def json(self): class NotificationType(StrEnum): + # Event Log Notification NEW_ORDER = "NewOrder" NEW_ORDER_COUNTERPART = "NewOrderCounterpart" CANCEL_ORDER = "CancelOrder" @@ -206,6 +225,9 @@ class NotificationType(StrEnum): CHANGE_TO_REDEEMED = "ChangeToRedeemed" CHANGE_TO_CANCELED = "ChangeToCanceled" + # Attribute Change Notification + TRANSFERABLE_CHANGED = "TransferableChanged" + class NotificationBlockNumber(Base): """Synchronized blockNumber of Notification""" @@ -226,3 +248,16 @@ class NotificationBlockNumber(Base): } FIELDS.update(Base.FIELDS) + + +class NotificationAttributeValue(Base): + """Synchronized attribute value for Notification""" + + __tablename__ = "notification_attribute_value" + + # contract address + contract_address = mapped_column(String(42), primary_key=True) + # attribute key + attribute_key = mapped_column(String(256), primary_key=True) + # attribute + attribute = mapped_column(JSON, nullable=False) diff --git a/app/model/schema/notification.py b/app/model/schema/notification.py index 333a78efe..64957ebcd 100644 --- a/app/model/schema/notification.py +++ b/app/model/schema/notification.py @@ -18,7 +18,7 @@ """ from enum import StrEnum -from typing import Optional +from typing import Literal, Optional from pydantic import BaseModel, Field @@ -47,6 +47,9 @@ class NotificationsSortItem(StrEnum): class NotificationsQuery(BasePaginationQuery): + notification_category: Optional[Literal["event_log", "attribute_change"]] = Field( + None + ) address: Optional[EthereumAddress] = Field(None, description="account address") notification_type: Optional[NotificationType] = Field(None) priority: Optional[int] = Field(None, ge=0, le=2) @@ -85,8 +88,9 @@ class NotificationMetainfo(BaseModel): class Notification(BaseModel): - notification_type: NotificationType = Field(examples=[NotificationType.NEW_ORDER]) + notification_category: Literal["event_log", "attribute_change"] id: str = Field(examples=["0x00000373ca8600000000000000"]) + notification_type: NotificationType = Field(examples=[NotificationType.NEW_ORDER]) priority: int block_timestamp: str = Field(description="block timestamp") is_read: bool diff --git a/batch/processor_Notifications_Coupon_Exchange.py b/batch/processor_Notifications_Coupon_Exchange.py index a9e214aa8..0c9d21e20 100644 --- a/batch/processor_Notifications_Coupon_Exchange.py +++ b/batch/processor_Notifications_Coupon_Exchange.py @@ -236,6 +236,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -276,6 +277,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -319,6 +321,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 2 @@ -359,6 +362,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 1) notification.notification_type = self.notification_type notification.priority = 1 @@ -399,6 +403,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 2) notification.notification_type = self.notification_type notification.priority = 2 @@ -439,6 +444,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 1) notification.notification_type = self.notification_type notification.priority = 1 @@ -482,6 +488,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 2) notification.notification_type = self.notification_type notification.priority = 1 @@ -522,6 +529,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 1) notification.notification_type = self.notification_type notification.priority = 2 @@ -565,6 +573,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 2) notification.notification_type = self.notification_type notification.priority = 2 diff --git a/batch/processor_Notifications_Membership_Exchange.py b/batch/processor_Notifications_Membership_Exchange.py index 28de790f7..7c0997ddb 100644 --- a/batch/processor_Notifications_Membership_Exchange.py +++ b/batch/processor_Notifications_Membership_Exchange.py @@ -236,6 +236,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -279,6 +280,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -322,6 +324,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 2 @@ -362,6 +365,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 1) notification.notification_type = self.notification_type notification.priority = 1 @@ -402,6 +406,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 2) notification.notification_type = self.notification_type notification.priority = 2 @@ -445,6 +450,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 1) notification.notification_type = self.notification_type notification.priority = 1 @@ -488,6 +494,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 2) notification.notification_type = self.notification_type notification.priority = 1 @@ -531,6 +538,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 1) notification.notification_type = self.notification_type notification.priority = 2 @@ -574,6 +582,7 @@ async def watch(self, db_session: AsyncSession, entries): } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry, 2) notification.notification_type = self.notification_type notification.priority = 2 diff --git a/batch/processor_Notifications_Token.py b/batch/processor_Notifications_Token.py index 1d3f74ad1..ae2166e10 100644 --- a/batch/processor_Notifications_Token.py +++ b/batch/processor_Notifications_Token.py @@ -26,8 +26,11 @@ from sqlalchemy import and_, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession +from web3 import Web3 from web3.contract import AsyncContract as Web3AsyncContract -from web3.exceptions import ABIEventNotFound +from web3.exceptions import ( + ABIEventNotFound, +) from web3.types import EventData from app.config import ( @@ -38,10 +41,12 @@ from app.contracts import AsyncContract from app.database import BatchAsyncSessionLocal from app.errors import ServiceUnavailable +from app.model.blockchain import BondToken, CouponToken, MembershipToken, ShareToken from app.model.db import ( IDXTokenListRegister, Listing, Notification, + NotificationAttributeValue, NotificationBlockNumber, NotificationType, ) @@ -67,8 +72,8 @@ token_list = TokenList(list_contract) -# Watcher -class Watcher: +# EventWatcher +class EventWatcher: contract_cache: dict[str, Web3AsyncContract] = {} def __init__( @@ -276,7 +281,7 @@ async def __set_synchronized_block_number( await db_session.merge(notification_block_number) -class WatchTransfer(Watcher): +class WatchTransfer(EventWatcher): """Watch Token Receive Event - Process for registering a notification when a token is received @@ -314,6 +319,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -324,7 +330,7 @@ async def db_merge( await db_session.merge(notification) -class WatchApplyForTransfer(Watcher): +class WatchApplyForTransfer(EventWatcher): """Watch Token ApplyForTransfer Event - Process for registering a notification when application for transfer is submitted @@ -359,6 +365,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -369,7 +376,7 @@ async def db_merge( await db_session.merge(notification) -class WatchApproveTransfer(Watcher): +class WatchApproveTransfer(EventWatcher): """Watch Token ApproveTransfer Event - Process for registering a notification when application for transfer is approved @@ -404,6 +411,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -414,7 +422,7 @@ async def db_merge( await db_session.merge(notification) -class WatchCancelTransfer(Watcher): +class WatchCancelTransfer(EventWatcher): """Watch Token CancelTransfer Event - Process for registering a notification when application for transfer is canceled @@ -449,6 +457,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -459,7 +468,7 @@ async def db_merge( await db_session.merge(notification) -class WatchForceLock(Watcher): +class WatchForceLock(EventWatcher): """Watch ForceLock Event - Process for registering a notification when a token is forcibly locked. @@ -490,6 +499,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -500,7 +510,7 @@ async def db_merge( await db_session.merge(notification) -class WatchForceUnlock(Watcher): +class WatchForceUnlock(EventWatcher): """Watch ForceUnlock Event - Process for registering a notification when a token is forcibly unlocked. @@ -531,6 +541,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -541,7 +552,7 @@ async def db_merge( await db_session.merge(notification) -class WatchChangeToRedeemed(Watcher): +class WatchChangeToRedeemed(EventWatcher): """Watch ChangeToRedeemed Event for Bond Token - Process for registering a notification when a token status is changed to redeemed. @@ -578,6 +589,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -588,7 +600,7 @@ async def db_merge( await db_session.merge(notification) -class WatchChangeToCanceled(Watcher): +class WatchChangeToCanceled(EventWatcher): """Watch ChangeToCanceled Event for Share Token - Process for registering a notification when a token status is changed to canceled. @@ -625,6 +637,7 @@ async def db_merge( "token_type": token_type, } notification = Notification() + notification.notification_category = "event_log" notification.notification_id = self._gen_notification_id(entry) notification.notification_type = self.notification_type notification.priority = 0 @@ -635,6 +648,262 @@ async def db_merge( await db_session.merge(notification) +# AttributeWatcher +class AttributeWatcher: + def __init__( + self, + attribute_key: str, + notification_type: str, + token_type_list: list[TokenType] = None, + ): + if token_type_list is None: + token_type_list = list([]) + self.notification_type = notification_type + self.token_type_list = token_type_list + self.attribute_key = attribute_key + self.attribute_key_hash = Web3.keccak(text=attribute_key).hex()[0:8] + + @staticmethod + def _gen_notification_id( + timestamp_ms: int, contract_address: str, attribute_key_hash: str, option_type=0 + ): + contract_address_without_prefix = contract_address.replace("0x", "").lower() + return f"0x{timestamp_ms:012x}{contract_address_without_prefix}{attribute_key_hash}{option_type:02x}" + + @staticmethod + async def _get_token_all_list( + db_session: AsyncSession, token_type_list: list[TokenType] + ): + _tokens = [] + + stmt = select(IDXTokenListRegister).join( + Listing, + and_(Listing.token_address == IDXTokenListRegister.token_address), + ) + if len(token_type_list) != 0: + stmt = stmt.where(IDXTokenListRegister.token_template.in_(token_type_list)) + registered_tokens: Sequence[IDXTokenListRegister] = ( + await db_session.scalars(stmt) + ).all() + for registered_token in registered_tokens: + _tokens.append( + { + "token": registered_token, + "token_type": registered_token.token_template, + } + ) + return _tokens + + async def db_merge( + self, + db_session: AsyncSession, + token_address: str, + token_type: str, + token_name: str, + token_owner_address: str, + previous_value: str | int | bool, + current_value: str | int | bool, + ): + pass + + async def loop(self): + start_time = time.time() + db_session = BatchAsyncSessionLocal() + + try: + # Get listed tokens + _token_list = await self._get_token_all_list( + db_session, self.token_type_list + ) + + for _token in _token_list: + try: + # Get previous attribute value from DB + previous_attribute_key_value = await self.__get_attribute_value( + db_session, + _token["token"].token_address, + self.attribute_key, + ) + is_initial_sync = ( + True if previous_attribute_key_value is None else False + ) + + # Get token detail by token type + if _token["token_type"] == TokenType.IbetStraightBond: + token_detail = await BondToken.get( + async_session=db_session, + token_address=_token["token"].token_address, + ) + elif _token["token_type"] == TokenType.IbetShare: + token_detail = await ShareToken.get( + async_session=db_session, + token_address=_token["token"].token_address, + ) + elif _token["token_type"] == TokenType.IbetCoupon: + token_detail = await CouponToken.get( + async_session=db_session, + token_address=_token["token"].token_address, + ) + elif _token["token_type"] == TokenType.IbetMembership: + token_detail = await MembershipToken.get( + async_session=db_session, + token_address=_token["token"].token_address, + ) + else: # pragma: no cover + continue + + # Get current attribute value from token detail + current_attribute_value = token_detail.__dict__.get( + self.attribute_key + ) + + if is_initial_sync is False: + # Get previous attribute value from DB record + previous_attribute_value = ( + previous_attribute_key_value.attribute.get( + self.attribute_key, None + ) + ) + # Register notification only if attribute value has changed + if ( + current_attribute_value is not None + and current_attribute_value != previous_attribute_value + ): + token_name = token_detail.name + # Register attribute change notification + await self.db_merge( + db_session=db_session, + token_address=_token["token"].token_address, + token_type=_token["token_type"], + token_owner_address=_token["token"].owner_address, + token_name=token_name, + previous_value=previous_attribute_value, + current_value=current_attribute_value, + ) + + # Save latest attribute value to DB + await self.__set_attribute_value( + db_session, + _token["token"].token_address, + self.attribute_key, + current_attribute_value, + ) + await db_session.commit() + + except Exception: # Continue processing even if an exception occurs + LOG.exception("Failed to watch attribute") + continue + + except SQLAlchemyError as sa_err: + LOG.error(f"A database error has occurred: code={sa_err.code}\n{sa_err}") + finally: + await db_session.close() + elapsed_time = time.time() - start_time + LOG.info( + "<{}> finished in {} secs".format(self.__class__.__name__, elapsed_time) + ) + + @staticmethod + async def __get_attribute_value( + db_session: AsyncSession, contract_address: str, attribute_key: str + ): + """Get latest synchronized attribute value""" + notification_attribute_value: NotificationAttributeValue | None = ( + await db_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == contract_address, + NotificationAttributeValue.attribute_key == attribute_key, + ) + ) + .limit(1) + ) + ).first() + return notification_attribute_value + + @staticmethod + async def __set_attribute_value( + db_session: AsyncSession, + contract_address: str, + attribute_key: str, + attribute_value: bool | int | str, + ): + """Set latest synchronized attribute value""" + notification_attribute_value: NotificationAttributeValue | None = ( + await db_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == contract_address, + NotificationAttributeValue.attribute_key == attribute_key, + ) + ) + .limit(1) + ) + ).first() + if notification_attribute_value is None: + notification_attribute_value = NotificationAttributeValue() + notification_attribute_value.contract_address = contract_address + notification_attribute_value.attribute_key = attribute_key + notification_attribute_value.attribute = {attribute_key: attribute_value} + await db_session.merge(notification_attribute_value) + + +class WatchTransferableAttribute(AttributeWatcher): + """Watch Transferable Attribute + + - Process for registering a notification when a token attribute "transferable" is changed. + """ + + def __init__(self): + super().__init__( + attribute_key="transferable", + notification_type=NotificationType.TRANSFERABLE_CHANGED, + token_type_list=[TokenType.IbetShare, TokenType.IbetStraightBond], + ) + + async def db_merge( + self, + db_session: AsyncSession, + token_address: str, + token_type: str, + token_name: str, + token_owner_address: str, + previous_value: str | int | bool, + current_value: str | int | bool, + ): + company_list = await CompanyList.get() + company = company_list.find(token_owner_address) + metadata = { + "company_name": company.corporate_name, + "token_address": token_address, + "token_name": token_name, + "exchange_address": "", + "token_type": token_type, + } + timestamp_ms = int(time.time() * 1000) + notification = Notification() + notification.notification_category = "attribute_change" + notification.notification_id = self._gen_notification_id( + timestamp_ms=timestamp_ms, + contract_address=token_address, + attribute_key_hash=self.attribute_key_hash, + ) + notification.notification_type = self.notification_type + notification.priority = 0 + notification.address = None + notification.block_timestamp = datetime.fromtimestamp(timestamp_ms / 1000) + notification.args = dict( + { + "previous": previous_value, + "current": current_value, + } + ) + notification.metainfo = metadata + await db_session.merge(notification) + + # メイン処理 async def main(): watchers = [ @@ -646,6 +915,7 @@ async def main(): WatchForceUnlock(), WatchChangeToRedeemed(), WatchChangeToCanceled(), + WatchTransferableAttribute(), ] LOG.info("Service started successfully") diff --git a/docs/ibet_wallet_api.yaml b/docs/ibet_wallet_api.yaml index ab04a8c69..6002ef2c3 100644 --- a/docs/ibet_wallet_api.yaml +++ b/docs/ibet_wallet_api.yaml @@ -5045,6 +5045,17 @@ paths: description: Limit for pagination title: Limit description: Limit for pagination + - name: notification_category + in: query + required: false + schema: + anyOf: + - enum: + - event_log + - attribute_change + type: string + - type: 'null' + title: Notification Category - name: address in: query required: false @@ -9295,15 +9306,21 @@ components: title: NotSupportedErrorResponse Notification: properties: - notification_type: - $ref: '#/components/schemas/NotificationType' - examples: - - NewOrder + notification_category: + type: string + enum: + - event_log + - attribute_change + title: Notification Category id: type: string title: Id examples: - '0x00000373ca8600000000000000' + notification_type: + $ref: '#/components/schemas/NotificationType' + examples: + - NewOrder priority: type: integer title: Priority @@ -9345,8 +9362,9 @@ components: description: datetime of create type: object required: - - notification_type + - notification_category - id + - notification_type - priority - block_timestamp - is_read @@ -9418,6 +9436,7 @@ components: - ForceUnlock - ChangeToRedeemed - ChangeToCanceled + - TransferableChanged title: NotificationType NotificationUpdateResponse: properties: diff --git a/migrations/versions/a5e395bf46a9_v25_9_0_feature_1662.py b/migrations/versions/a5e395bf46a9_v25_9_0_feature_1662.py new file mode 100644 index 000000000..07a39c8a8 --- /dev/null +++ b/migrations/versions/a5e395bf46a9_v25_9_0_feature_1662.py @@ -0,0 +1,64 @@ +"""v25_9_0_feature_1662 + +Revision ID: a5e395bf46a9 +Revises: 819325835c3d +Create Date: 2025-07-31 12:02:12.029337 + +""" + +from alembic import op +import sqlalchemy as sa + + +from app.database import get_db_schema +from app.model.db import Notification + +# revision identifiers, used by Alembic. +revision = "a5e395bf46a9" +down_revision = "819325835c3d" +branch_labels = None +depends_on = None + + +def upgrade(): + connection = op.get_bind() + + op.create_table( + "notification_attribute_value", + sa.Column("contract_address", sa.String(length=42), nullable=False), + sa.Column("attribute_key", sa.String(length=256), nullable=False), + sa.Column("attribute", sa.JSON(), nullable=False), + sa.Column("created", sa.DateTime(), nullable=True), + sa.Column("modified", sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint("contract_address", "attribute_key"), + schema=get_db_schema(), + ) + op.add_column( + "notification", + sa.Column("notification_category", sa.String(length=20), nullable=True), + schema=get_db_schema(), + ) + op.get_bind().execute( + sa.update(Notification).values(notification_category="event_log") + ) + op.alter_column( + "notification", + "notification_category", + existing_type=sa.String(length=20), + nullable=False, + schema=get_db_schema(), + ) + op.drop_constraint("notification_pkey", "notification", type_="primary") + op.create_primary_key( + "notification_pkey", + "notification", + ["notification_category", "notification_id"], + schema=get_db_schema(), + ) + + +def downgrade(): + connection = op.get_bind() + + op.drop_column("notification", "notification_category", schema=get_db_schema()) + op.drop_table("notification_attribute_value", schema=get_db_schema()) diff --git a/tests/app/notification_NotificationsCount_test.py b/tests/app/notification_NotificationsCount_test.py index af4e73edd..f16079cbf 100644 --- a/tests/app/notification_NotificationsCount_test.py +++ b/tests/app/notification_NotificationsCount_test.py @@ -33,6 +33,7 @@ class TestNotificationCount: def _insert_test_data(self, session): n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034300000000000000" n.notification_type = "SampleNotification1" n.priority = 1 @@ -51,6 +52,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034000000000000000" n.notification_type = "SampleNotification2" n.priority = 1 @@ -67,6 +69,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011034000000000000000" n.notification_type = "SampleNotification3" n.priority = 2 @@ -83,6 +86,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011032000000000000000" n.notification_type = "SampleNotification4" n.priority = 1 @@ -99,6 +103,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000001034000000000000000" n.notification_type = "SampleNotification5" n.priority = 0 diff --git a/tests/app/notification_NotificationsId_DELETE_test.py b/tests/app/notification_NotificationsId_DELETE_test.py index 2ec072332..eb72a085a 100644 --- a/tests/app/notification_NotificationsId_DELETE_test.py +++ b/tests/app/notification_NotificationsId_DELETE_test.py @@ -36,6 +36,7 @@ class TestNotificationsIdDELETE: def _insert_test_data(self, session): n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034300000000000000" n.notification_type = "NewOrder" n.priority = 1 @@ -55,6 +56,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034000000000000000" n.notification_type = "NewOrderCounterpart" n.priority = 1 @@ -72,6 +74,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011034000000000000000" n.notification_type = "NewOrder" n.priority = 2 @@ -89,6 +92,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011032000000000000000" n.notification_type = "NewOrderCounterpart" n.priority = 1 @@ -106,6 +110,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000001034000000000000000" n.notification_type = "NewOrder" n.priority = 0 diff --git a/tests/app/notification_NotificationsId_POST_test.py b/tests/app/notification_NotificationsId_POST_test.py index 3c3108dae..2b14ca278 100644 --- a/tests/app/notification_NotificationsId_POST_test.py +++ b/tests/app/notification_NotificationsId_POST_test.py @@ -38,6 +38,7 @@ def _insert_test_data(self, session): self.session = session # HACK: updateでcommitされてしまう対策 n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034300000000000000" n.notification_type = NotificationType.NEW_ORDER n.priority = 1 @@ -56,6 +57,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034000000000000000" n.notification_type = NotificationType.APPROVE_TRANSFER n.priority = 1 @@ -72,6 +74,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011034000000000000000" n.notification_type = NotificationType.APPLY_FOR_TRANSFER n.priority = 2 @@ -88,6 +91,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011032000000000000000" n.notification_type = NotificationType.BUY_AGREEMENT n.priority = 1 @@ -104,6 +108,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = NotificationType.BUY_SETTLEMENT_NG n.notification_type = "SampleNotification5" n.priority = 0 diff --git a/tests/app/notification_NotificationsRead_test.py b/tests/app/notification_NotificationsRead_test.py index f5fbb4acc..3ea39c0fa 100644 --- a/tests/app/notification_NotificationsRead_test.py +++ b/tests/app/notification_NotificationsRead_test.py @@ -44,6 +44,7 @@ def _insert_test_data(self, session): self.session = session # HACK: updateでcommitされてしまう対策 n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034300000000000000" n.notification_type = "SampleNotification1" n.priority = 1 @@ -64,6 +65,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034000000000000000" n.notification_type = "SampleNotification2" n.priority = 1 @@ -80,6 +82,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011034000000000000000" n.notification_type = "SampleNotification3" n.priority = 2 @@ -96,6 +99,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011032000000000000000" n.notification_type = "SampleNotification4" n.priority = 1 @@ -112,6 +116,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000001034000000000000000" n.notification_type = "SampleNotification5" n.priority = 0 diff --git a/tests/app/notification_Notifications_GET_test.py b/tests/app/notification_Notifications_GET_test.py index c27d966ba..a40504cf8 100644 --- a/tests/app/notification_Notifications_GET_test.py +++ b/tests/app/notification_Notifications_GET_test.py @@ -35,6 +35,7 @@ class TestNotificationsGet: def _insert_test_data(self, session): n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034300000000000000" n.notification_type = "NewOrder" n.priority = 1 @@ -54,6 +55,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000021034000000000000000" n.notification_type = "NewOrderCounterpart" n.priority = 1 @@ -71,6 +73,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011034000000000000000" n.notification_type = "NewOrder" n.priority = 2 @@ -88,6 +91,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000011032000000000000000" n.notification_type = "NewOrderCounterpart" n.priority = 1 @@ -105,6 +109,7 @@ def _insert_test_data(self, session): session.add(n) n = Notification() + n.notification_category = "event_log" n.notification_id = "0x00000001034000000000000000" n.notification_type = "NewOrder" n.priority = 0 @@ -140,6 +145,7 @@ def test_normal_1(self, client: TestClient, session: Session): "result_set": {"count": 5, "offset": None, "limit": None, "total": 5}, "notifications": [ { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000021034300000000000000", "sort_id": 1, @@ -157,6 +163,7 @@ def test_normal_1(self, client: TestClient, session: Session): "created": "2022/01/01 15:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrderCounterpart", "id": "0x00000021034000000000000000", "sort_id": 2, @@ -174,6 +181,7 @@ def test_normal_1(self, client: TestClient, session: Session): "created": "2022/01/01 16:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000011034000000000000000", "sort_id": 3, @@ -191,6 +199,7 @@ def test_normal_1(self, client: TestClient, session: Session): "created": "2022/01/01 17:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrderCounterpart", "id": "0x00000011032000000000000000", "sort_id": 4, @@ -208,6 +217,7 @@ def test_normal_1(self, client: TestClient, session: Session): "created": "2022/01/01 18:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000001034000000000000000", "sort_id": 5, @@ -252,6 +262,7 @@ def test_normal_2(self, client: TestClient, session: Session): "result_set": {"count": 5, "offset": 1, "limit": 2, "total": 5}, "notifications": [ { + "notification_category": "event_log", "notification_type": "NewOrderCounterpart", "id": "0x00000021034000000000000000", "sort_id": 2, @@ -269,6 +280,7 @@ def test_normal_2(self, client: TestClient, session: Session): "created": "2022/01/01 16:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000011034000000000000000", "sort_id": 3, @@ -332,6 +344,7 @@ def test_normal_4(self, client: TestClient, session: Session): "address": self.address, "notification_type": "NewOrder", "priority": 2, + "notification_category": "event_log", }, ) @@ -339,6 +352,7 @@ def test_normal_4(self, client: TestClient, session: Session): "result_set": {"count": 1, "offset": None, "limit": None, "total": 5}, "notifications": [ { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000011034000000000000000", "sort_id": 1, @@ -410,6 +424,7 @@ def test_normal_6(self, client: TestClient, session: Session): "result_set": {"count": 5, "offset": None, "limit": None, "total": 5}, "notifications": [ { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000011034000000000000000", "sort_id": 1, @@ -427,6 +442,7 @@ def test_normal_6(self, client: TestClient, session: Session): "created": "2022/01/01 17:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000021034300000000000000", "sort_id": 2, @@ -444,6 +460,7 @@ def test_normal_6(self, client: TestClient, session: Session): "created": "2022/01/01 15:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrderCounterpart", "id": "0x00000021034000000000000000", "sort_id": 3, @@ -461,6 +478,7 @@ def test_normal_6(self, client: TestClient, session: Session): "created": "2022/01/01 16:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrderCounterpart", "id": "0x00000011032000000000000000", "sort_id": 4, @@ -478,6 +496,7 @@ def test_normal_6(self, client: TestClient, session: Session): "created": "2022/01/01 18:20:30", }, { + "notification_category": "event_log", "notification_type": "NewOrder", "id": "0x00000001034000000000000000", "sort_id": 5, @@ -544,10 +563,10 @@ def test_error_1(self, client: TestClient, session: Session): { "type": "enum", "loc": ["query", "notification_type"], - "msg": "Input should be 'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock', 'ForceUnlock', 'ChangeToRedeemed' or 'ChangeToCanceled'", + "msg": "Input should be 'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock', 'ForceUnlock', 'ChangeToRedeemed', 'ChangeToCanceled' or 'TransferableChanged'", "input": "hoge", "ctx": { - "expected": "'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock', 'ForceUnlock', 'ChangeToRedeemed' or 'ChangeToCanceled'" + "expected": "'NewOrder', 'NewOrderCounterpart', 'CancelOrder', 'CancelOrderCounterpart', 'ForceCancelOrder', 'BuyAgreement', 'BuySettlementOK', 'BuySettlementNG', 'SellAgreement', 'SellSettlementOK', 'SellSettlementNG', 'Transfer', 'ApplyForTransfer', 'ApproveTransfer', 'CancelTransfer', 'ForceLock', 'ForceUnlock', 'ChangeToRedeemed', 'ChangeToCanceled' or 'TransferableChanged'" }, }, { diff --git a/tests/batch/processor_Notifications_Coupon_Exchange_test.py b/tests/batch/processor_Notifications_Coupon_Exchange_test.py index 3614d3de2..3a0d0fdd5 100644 --- a/tests/batch/processor_Notifications_Coupon_Exchange_test.py +++ b/tests/batch/processor_Notifications_Coupon_Exchange_test.py @@ -133,6 +133,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 0 ) @@ -212,6 +213,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 0 ) @@ -237,6 +239,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 0 ) @@ -383,6 +386,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -461,6 +465,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 0 ) @@ -486,6 +491,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -657,6 +663,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -763,6 +770,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 0 ) @@ -790,6 +798,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -961,6 +970,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -1041,6 +1051,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 1 ) @@ -1067,6 +1078,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -1226,6 +1238,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) @@ -1304,6 +1317,7 @@ async def test_normal_2( ).all() assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 2 ) @@ -1329,6 +1343,7 @@ async def test_normal_2( "token_type": "IbetCoupon", } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) @@ -1493,6 +1508,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 1 ) @@ -1581,6 +1597,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 1 ) @@ -1609,6 +1626,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 1 ) @@ -1780,6 +1798,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 2 ) @@ -1868,6 +1887,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 2 ) @@ -1896,6 +1916,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 2 ) @@ -2067,6 +2088,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -2155,6 +2177,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 1 ) @@ -2183,6 +2206,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -2354,6 +2378,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) @@ -2442,6 +2467,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 2 ) @@ -2470,6 +2496,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) diff --git a/tests/batch/processor_Notifications_Membership_Exchange_test.py b/tests/batch/processor_Notifications_Membership_Exchange_test.py index 4b6e87a40..4e934ac55 100644 --- a/tests/batch/processor_Notifications_Membership_Exchange_test.py +++ b/tests/batch/processor_Notifications_Membership_Exchange_test.py @@ -133,6 +133,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 0 ) @@ -204,6 +205,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 0 ) @@ -229,6 +231,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 0 ) @@ -374,6 +377,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -451,6 +455,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 0 ) @@ -476,6 +481,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -646,6 +652,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -752,6 +759,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 0 ) @@ -779,6 +787,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 0 ) @@ -948,6 +957,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -1027,6 +1037,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 1 ) @@ -1053,6 +1064,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -1210,6 +1222,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) @@ -1289,6 +1302,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 2 ) @@ -1315,6 +1329,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) @@ -1475,6 +1490,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 1 ) @@ -1560,6 +1576,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 1 ) @@ -1588,6 +1605,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 1 ) @@ -1753,6 +1771,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 2 ) @@ -1838,6 +1857,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 1, 2 ) @@ -1866,6 +1886,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 1, 2 ) @@ -2031,6 +2052,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -2116,6 +2138,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 1 ) @@ -2144,6 +2167,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 1 ) @@ -2309,6 +2333,7 @@ async def test_normal_1( select(Notification).order_by(Notification.created).limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) @@ -2394,6 +2419,7 @@ async def test_normal_2( assert len(_notification_list) == 2 _notification = _notification_list[0] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number - 1, 0, 0, 2 ) @@ -2422,6 +2448,7 @@ async def test_normal_2( } _notification = _notification_list[1] + assert _notification.notification_category == "event_log" assert _notification.notification_id == "0x{:012x}{:06x}{:06x}{:02x}".format( block_number, 0, 0, 2 ) diff --git a/tests/batch/processor_Notifications_Token_test.py b/tests/batch/processor_Notifications_Token_test.py index 6293133dc..0342a5cce 100644 --- a/tests/batch/processor_Notifications_Token_test.py +++ b/tests/batch/processor_Notifications_Token_test.py @@ -19,6 +19,7 @@ from __future__ import annotations +from datetime import datetime, timezone from importlib import reload from typing import TYPE_CHECKING, Callable from unittest import mock @@ -33,9 +34,11 @@ from app import config from app.model.db import ( + IDXShareToken, IDXTokenListRegister, Listing, Notification, + NotificationAttributeValue, NotificationBlockNumber, NotificationType, ) @@ -61,10 +64,11 @@ share_set_transfer_approval_required, transfer_coupon_token, transfer_share_token, + untransferable_share_token, ) if TYPE_CHECKING: - from batch.processor_Notifications_Token import Watcher + from batch.processor_Notifications_Token import EventWatcher web3 = Web3(Web3.HTTPProvider(config.WEB3_HTTP_PROVIDER)) web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) @@ -73,7 +77,7 @@ @pytest.fixture(scope="function") def watcher_factory( async_session: AsyncSession, shared_contract: SharedContract -) -> Callable[[str], Watcher]: +) -> Callable[[str], EventWatcher]: def _watcher(cls_name): config.TOKEN_LIST_CONTRACT_ADDRESS = shared_contract["TokenList"]["address"] @@ -274,6 +278,7 @@ async def test_normal_1( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -355,6 +360,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -382,6 +388,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader2["account_address"] @@ -516,6 +523,7 @@ async def test_normal_4( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.TRANSFER assert _notification.priority == 0 assert _notification.address == exchange_contract["address"] @@ -744,6 +752,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.APPLY_FOR_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader2["account_address"] @@ -773,6 +782,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.APPLY_FOR_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader2["account_address"] @@ -980,6 +990,7 @@ async def test_normal_1( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.APPROVE_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1073,6 +1084,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.APPROVE_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1101,6 +1113,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.APPROVE_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1308,6 +1321,7 @@ async def test_normal_1( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1401,6 +1415,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1429,6 +1444,7 @@ async def test_normal_2( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1546,6 +1562,7 @@ async def test_normal_3( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1574,6 +1591,7 @@ async def test_normal_3( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1602,6 +1620,7 @@ async def test_normal_3( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1630,6 +1649,7 @@ async def test_normal_3( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.CANCEL_TRANSFER assert _notification.priority == 0 assert _notification.address == self.trader["account_address"] @@ -1836,6 +1856,7 @@ async def test_normal_1( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.FORCE_LOCK assert _notification.priority == 0 assert _notification.address == self.issuer["account_address"] @@ -2100,6 +2121,7 @@ async def test_normal_1( .limit(1) ) ).first() + assert _notification.notification_category == "event_log" assert _notification.notification_type == NotificationType.FORCE_UNLOCK assert _notification.priority == 0 assert _notification.address == self.issuer["account_address"] @@ -2308,7 +2330,6 @@ async def test_error_1( @pytest.mark.asyncio class TestWatchChangeToRedeemed: issuer = eth_account["issuer"] - lock_account = eth_account["user1"] ########################################################################### # Normal Case @@ -2423,6 +2444,7 @@ async def test_normal_2( _notification_list = (await async_session.scalars(select(Notification))).all() assert len(_notification_list) == 1 + assert _notification_list[0].notification_category == "event_log" assert ( _notification_list[0].notification_type == NotificationType.CHANGE_TO_REDEEMED @@ -2507,6 +2529,7 @@ async def test_normal_3( _notification_list = (await async_session.scalars(select(Notification))).all() assert len(_notification_list) == 2 + assert _notification_list[0].notification_category == "event_log" assert ( _notification_list[0].notification_type == NotificationType.CHANGE_TO_REDEEMED @@ -2521,6 +2544,7 @@ async def test_normal_3( "token_name": "テスト債券", "token_type": "IbetStraightBond", } + assert _notification_list[1].notification_category == "event_log" assert ( _notification_list[1].notification_type == NotificationType.CHANGE_TO_REDEEMED @@ -2703,7 +2727,6 @@ async def test_error_1( @pytest.mark.asyncio class TestWatchChangeToCanceled: issuer = eth_account["issuer"] - lock_account = eth_account["user1"] ########################################################################### # Normal Case @@ -2818,6 +2841,7 @@ async def test_normal_2( _notification_list = (await async_session.scalars(select(Notification))).all() assert len(_notification_list) == 1 + assert _notification_list[0].notification_category == "event_log" assert ( _notification_list[0].notification_type == NotificationType.CHANGE_TO_CANCELED @@ -2902,6 +2926,7 @@ async def test_normal_3( _notification_list = (await async_session.scalars(select(Notification))).all() assert len(_notification_list) == 2 + assert _notification_list[0].notification_category == "event_log" assert ( _notification_list[0].notification_type == NotificationType.CHANGE_TO_CANCELED @@ -2916,6 +2941,7 @@ async def test_normal_3( "token_name": "テスト株式", "token_type": "IbetShare", } + assert _notification_list[1].notification_category == "event_log" assert ( _notification_list[1].notification_type == NotificationType.CHANGE_TO_CANCELED @@ -3093,3 +3119,362 @@ async def test_error_1( await async_session.scalars(select(NotificationBlockNumber).limit(1)) ).first() assert _notification_block_number is None + + +@pytest.mark.asyncio +class TestWatchWatchTransferableAttribute: + issuer = eth_account["issuer"] + + ########################################################################### + # Normal Case + ########################################################################### + + # + # Initial Sync + async def test_normal_1( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchTransferableAttribute") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + _notification = ( + await async_session.scalars(select(Notification).limit(1)) + ).first() + assert _notification is None + + _notification_attribute_value: NotificationAttributeValue = ( + await async_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == token["address"], + NotificationAttributeValue.attribute_key == "transferable", + ) + ) + .limit(1) + ) + ).first() + assert _notification_attribute_value.attribute == {"transferable": True} + + # + # Attribute not changed + @pytest.mark.freeze_time(datetime(2025, 7, 31, 1, 35, 0, tzinfo=timezone.utc)) + async def test_normal_2( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchTransferableAttribute") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token = IDXShareToken() + idx_token.token_address = token["address"] + idx_token.token_template = "IbetShare" + idx_token.name = "test_token" + idx_token.transferable = True + idx_token.short_term_cache_created = datetime( + 2025, 7, 31, 1, 35, 0, tzinfo=timezone.utc + ) + async_session.add(idx_token) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + + notification_attribute_value = NotificationAttributeValue() + notification_attribute_value.contract_address = token["address"] + notification_attribute_value.attribute_key = "transferable" + notification_attribute_value.attribute = {"transferable": True} + async_session.add(notification_attribute_value) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + async_session.expunge_all() + _notification = ( + await async_session.scalars(select(Notification).limit(1)) + ).first() + assert _notification is None + + _notification_attribute_value: NotificationAttributeValue = ( + await async_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == token["address"], + NotificationAttributeValue.attribute_key == "transferable", + ) + ) + .limit(1) + ) + ).first() + assert _notification_attribute_value.attribute == {"transferable": True} + + # + # Attribute changed + @pytest.mark.freeze_time(datetime(2025, 7, 31, 1, 35, 0, tzinfo=timezone.utc)) + async def test_normal_3( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchTransferableAttribute") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token = IDXShareToken() + idx_token.token_address = token["address"] + idx_token.token_template = "IbetShare" + idx_token.name = "test_token" + idx_token.transferable = False + idx_token.short_term_cache_created = datetime( + 2025, 7, 31, 1, 35, 0, tzinfo=timezone.utc + ) + async_session.add(idx_token) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + + notification_attribute_value = NotificationAttributeValue() + notification_attribute_value.contract_address = token["address"] + notification_attribute_value.attribute_key = "transferable" + notification_attribute_value.attribute = {"transferable": True} + async_session.add(notification_attribute_value) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + async_session.expunge_all() + _notification = ( + await async_session.scalars(select(Notification).limit(1)) + ).first() + attribute_key_hash = Web3.keccak(text="transferable").hex()[0:8] + assert _notification.notification_category == "attribute_change" + assert ( + _notification.notification_id[14:] + == f"{token['address'].replace('0x', '').lower()}{attribute_key_hash}00" + ) + assert _notification.notification_type == NotificationType.TRANSFERABLE_CHANGED + assert _notification.priority == 0 + assert _notification.address is None + assert _notification.block_timestamp is not None + assert _notification.args == { + "previous": True, + "current": False, + } + assert _notification.metainfo == { + "company_name": "株式会社DEMO", + "token_address": token["address"], + "token_name": "test_token", + "exchange_address": "", + "token_type": "IbetShare", + } + + _notification_attribute_value: NotificationAttributeValue = ( + await async_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == token["address"], + NotificationAttributeValue.attribute_key == "transferable", + ) + ) + .limit(1) + ) + ).first() + assert _notification_attribute_value.attribute == {"transferable": False} + + # + # Attribute changed (On chain access) + @pytest.mark.freeze_time(datetime(2025, 7, 31, 1, 35, 0, tzinfo=timezone.utc)) + async def test_normal_4( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchTransferableAttribute") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + untransferable_share_token(self.issuer, token) + + idx_token = IDXShareToken() + idx_token.token_address = token["address"] + idx_token.token_template = "IbetShare" + idx_token.name = "test_token" + idx_token.transferable = True # Cache expired + idx_token.short_term_cache_created = datetime( + 2025, 7, 30, 1, 35, 0, tzinfo=timezone.utc + ) # Cache expired + async_session.add(idx_token) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + + notification_attribute_value = NotificationAttributeValue() + notification_attribute_value.contract_address = token["address"] + notification_attribute_value.attribute_key = "transferable" + notification_attribute_value.attribute = {"transferable": True} + async_session.add(notification_attribute_value) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + async_session.expunge_all() + _notification = ( + await async_session.scalars(select(Notification).limit(1)) + ).first() + attribute_key_hash = Web3.keccak(text="transferable").hex()[0:8] + assert _notification.notification_category == "attribute_change" + assert ( + _notification.notification_id[14:] + == f"{token['address'].replace('0x', '').lower()}{attribute_key_hash}00" + ) + assert _notification.notification_type == NotificationType.TRANSFERABLE_CHANGED + assert _notification.priority == 0 + assert _notification.address is None + assert _notification.block_timestamp is not None + assert _notification.args == { + "previous": True, + "current": False, + } + assert _notification.metainfo == { + "company_name": "株式会社DEMO", + "token_address": token["address"], + "token_name": "test_token", + "exchange_address": "", + "token_type": "IbetShare", + } + + _notification_attribute_value: NotificationAttributeValue = ( + await async_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == token["address"], + NotificationAttributeValue.attribute_key == "transferable", + ) + ) + .limit(1) + ) + ).first() + assert _notification_attribute_value.attribute == {"transferable": False} + + # ########################################################################### + # # Error Case + # ########################################################################### + + # + # Error occur + @mock.patch( + "app.model.blockchain.token.ShareToken.get", + MagicMock(side_effect=Exception()), + ) + async def test_error_1( + self, watcher_factory, async_session, shared_contract, mocked_company_list + ): + watcher = watcher_factory("WatchTransferableAttribute") + exchange_contract = shared_contract["IbetShareExchange"] + token_list_contract = shared_contract["TokenList"] + personal_info_contract = shared_contract["PersonalInfo"] + + # Issue token + token = await prepare_share_token( + self.issuer, + exchange_contract, + token_list_contract, + personal_info_contract, + async_session, + ) + + idx_token_list_item = IDXTokenListRegister() + idx_token_list_item.token_address = token["address"] + idx_token_list_item.owner_address = self.issuer["account_address"] + idx_token_list_item.token_template = "IbetShare" + async_session.add(idx_token_list_item) + await async_session.commit() + + # Run target process + await watcher.loop() + + # Assertion + async_session.expunge_all() + _notification = ( + await async_session.scalars(select(Notification).limit(1)) + ).first() + assert _notification is None + + _notification_attribute_value: NotificationAttributeValue = ( + await async_session.scalars( + select(NotificationAttributeValue) + .where( + and_( + NotificationAttributeValue.contract_address == token["address"], + NotificationAttributeValue.attribute_key == "transferable", + ) + ) + .limit(1) + ) + ).first() + assert _notification_attribute_value is None From c17585c5835679c6bb72da62ada6f2fc8a9db469 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:34:51 +0900 Subject: [PATCH 10/22] Bump hardhat from 2.26.1 to 2.26.2 (#1665) Bumps [hardhat](https://github.com/nomiclabs/hardhat) from 2.26.1 to 2.26.2. - [Release notes](https://github.com/nomiclabs/hardhat/releases) - [Commits](https://github.com/nomiclabs/hardhat/compare/hardhat@2.26.1...hardhat@2.26.2) --- updated-dependencies: - dependency-name: hardhat dependency-version: 2.26.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d0cf8de5..61d122633 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "hardhat": "^2.26.1" + "hardhat": "^2.26.2" } }, "node_modules/@ethereumjs/rlp": { @@ -1615,9 +1615,9 @@ "license": "ISC" }, "node_modules/hardhat": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.26.1.tgz", - "integrity": "sha512-CXWuUaTtehxiHPCdlitntctfeYRgujmXkNX5gnrD5jdA6HhRQt+WWBZE/gHXbE29y/wDmmUL2d652rI0ctjqjw==", + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.26.2.tgz", + "integrity": "sha512-uIETdC1MAWSnu+48B+583r3b8JqHtkBGEiXoAPV5hcXYGkCW5Fnnpn7SIgPa8owhObm9oUqIqMXMJ6H92N7Pyg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3827c376d..9b4cb518e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "devDependencies": { - "hardhat": "^2.26.1" + "hardhat": "^2.26.2" } } From 9e3fe67f23af37bbd466823bc4b0400d8824d2e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:56:20 +0900 Subject: [PATCH 11/22] Bump actions/checkout from 4 to 5 (#1666) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/pr.yml | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 093d5a902..c4c2b98b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a6f6dfd06..784453355 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2bc5ee80f..6f48fe119 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,13 +6,13 @@ jobs: name: 'Build docker image' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/setup-test-image lint-ruff: name: 'Lint check (ruff)' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: astral-sh/ruff-action@v3 unit-test: name: 'Unit tests' @@ -27,7 +27,7 @@ jobs: 'tests/batch/', ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/setup-test-image - name: build image on compose run: docker compose build @@ -59,7 +59,7 @@ jobs: packages: read pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/setup-test-image - name: build image on compose run: docker compose build @@ -115,7 +115,7 @@ jobs: matrix: db: [postgres, mysql] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/setup-test-image - name: run unit test using ${{ matrix.db }} run: docker compose run ibet-wallet-api-${{ matrix.db }} bash --login -c "cd /app/ibet-Wallet-API && uv run pytest -vv --test-alembic -m 'alembic'" From 704b12f8f676706a3524e8194cccd668c1ddc22b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:05:50 +0900 Subject: [PATCH 12/22] Bump actions/download-artifact from 4 to 5 (#1667) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6f48fe119..daaf305f4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -67,7 +67,7 @@ jobs: run: | sudo chown runner:docker /home/runner/work/ibet-Wallet-API/ibet-Wallet-API/cov sudo chmod 777 /home/runner/work/ibet-Wallet-API/ibet-Wallet-API/cov - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: pattern: '.coverage-*' path: cov/ @@ -76,7 +76,7 @@ jobs: run: docker compose run -w /app/ibet-Wallet-API/cov ibet-wallet-api-postgres bash --login -c 'uv run coverage combine .coverage-*' - name: generate coverage xml file run: docker compose run -w /app/ibet-Wallet-API ibet-wallet-api-postgres bash --login -c 'uv run coverage xml --data-file=cov/.coverage -o cov/coverage.xml' - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: pattern: 'pytest-*' path: cov/ From 0086a405f29c5d67b7acfba0223577fc07ecd3f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:09:29 +0900 Subject: [PATCH 13/22] Bump MishaKav/pytest-coverage-comment from 1.1.54 to 1.1.56 (#1668) Bumps [MishaKav/pytest-coverage-comment](https://github.com/mishakav/pytest-coverage-comment) from 1.1.54 to 1.1.56. - [Release notes](https://github.com/mishakav/pytest-coverage-comment/releases) - [Changelog](https://github.com/MishaKav/pytest-coverage-comment/blob/main/CHANGELOG.md) - [Commits](https://github.com/mishakav/pytest-coverage-comment/compare/13d3c18e21895566c746187c9ea74736372e5e91...9638e4b1448019aba40c4aaaa1ade87a9f211aa1) --- updated-dependencies: - dependency-name: MishaKav/pytest-coverage-comment dependency-version: 1.1.56 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index daaf305f4..6e734afe4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -90,7 +90,7 @@ jobs: run: jrm cov/pytest.xml "cov/pytest-*.xml" - name: Pytest coverage comment id: coverageComment - uses: MishaKav/pytest-coverage-comment@13d3c18e21895566c746187c9ea74736372e5e91 # v1.1.54 + uses: MishaKav/pytest-coverage-comment@9638e4b1448019aba40c4aaaa1ade87a9f211aa1 # v1.1.56 with: report-only-changed-files: true pytest-xml-coverage-path: cov/coverage.xml From a9745c21363a48739c30f7563452882563b82ecf Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Mon, 18 Aug 2025 16:16:03 +0900 Subject: [PATCH 14/22] Refactor transaction retrieval to handle pruned transactions and improve error handling (#1670) --- app/api/routers/eth.py | 10 ++-- app/contracts/contract.py | 32 ++++++++++- batch/indexer_Position_Bond.py | 20 +++++-- batch/indexer_Position_Share.py | 20 +++++-- batch/indexer_Transfer.py | 4 +- tests/batch/indexer_Position_Bond_test.py | 62 ++++++++++++++++++---- tests/batch/indexer_Position_Share_test.py | 62 ++++++++++++++++++---- tests/batch/indexer_Transfer_test.py | 16 ++++-- 8 files changed, 183 insertions(+), 43 deletions(-) diff --git a/app/api/routers/eth.py b/app/api/routers/eth.py index 8ba8dc70a..aa4dc3416 100644 --- a/app/api/routers/eth.py +++ b/app/api/routers/eth.py @@ -28,7 +28,7 @@ from rlp import decode from sqlalchemy import select from web3.exceptions import ContractLogicError, TimeExhausted, Web3RPCError -from web3.types import TxReceipt +from web3.types import BlockIdentifier, TxReceipt from app import config, log from app.contracts import AsyncContract @@ -286,7 +286,7 @@ async def send_raw_transaction( ) if tx["status"] == 0: # inspect reason of transaction fail - err_msg = await inspect_tx_failure(tx_hash) + err_msg = await inspect_tx_failure(tx_hash, tx["blockNumber"]) code, message = error_code_msg(err_msg) result.append( { @@ -518,7 +518,7 @@ async def wait_for_transaction_receipt( ) if tx["status"] == 0: # Inspect reason of transaction fail. - err_msg = await inspect_tx_failure(transaction_hash) + err_msg = await inspect_tx_failure(transaction_hash, tx["blockNumber"]) code, message = error_code_msg(err_msg) result["status"] = 0 result["error_code"] = code @@ -531,8 +531,8 @@ async def wait_for_transaction_receipt( return json_response({**SuccessResponse.default(), "data": result}) -async def inspect_tx_failure(tx_hash: HexBytes) -> str: - tx = await async_web3.eth.get_transaction(tx_hash) +async def inspect_tx_failure(tx_hash: HexBytes, block_number: BlockIdentifier) -> str: + tx = await AsyncContract.get_transaction(tx_hash, block_number) # build a new transaction to replay: replay_tx = { diff --git a/app/contracts/contract.py b/app/contracts/contract.py index 3a3bfcbd7..d4613de60 100644 --- a/app/contracts/contract.py +++ b/app/contracts/contract.py @@ -18,13 +18,19 @@ """ import json -from typing import Type, TypeVar +from typing import Any, Never, Type, TypeVar from eth_utils import to_checksum_address +from hexbytes import HexBytes from web3 import contract from web3.contract.async_contract import AsyncContractEvents from web3.eth.async_eth import AsyncContract as Web3AsyncContract -from web3.exceptions import BadFunctionCallOutput, ContractLogicError +from web3.exceptions import ( + BadFunctionCallOutput, + ContractLogicError, + TransactionNotFound, +) +from web3.types import BlockIdentifier, TxData from app.utils.web3_utils import AsyncWeb3Wrapper @@ -134,3 +140,25 @@ async def call_function( raise exc return result + + @staticmethod + async def get_transaction( + transaction_hash: HexBytes, block_number: BlockIdentifier + ) -> TxData | dict[Any, Never]: + """Get transaction + + :param transaction_hash: Transaction hash + :param block_number: Block number + :return: Return the transaction data or empty dict + """ + tx = {} + try: + tx = await async_web3.eth.get_transaction(transaction_hash) + except TransactionNotFound: + # Retrieve transaction from block data when node has pruned old transaction + block = await async_web3.eth.get_block(block_number, full_transactions=True) + for transaction in block.get("transactions", []): + if transaction.get("hash") == transaction_hash: + tx = transaction + break + return tx diff --git a/batch/indexer_Position_Bond.py b/batch/indexer_Position_Bond.py index 63478b54a..b77943a3c 100644 --- a/batch/indexer_Position_Bond.py +++ b/batch/indexer_Position_Bond.py @@ -414,7 +414,9 @@ async def __sync_lock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Lock event @@ -496,7 +498,9 @@ async def __sync_force_lock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Lock event @@ -580,7 +584,9 @@ async def __sync_unlock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Unlock event @@ -664,7 +670,9 @@ async def __sync_force_unlock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Unlock event @@ -751,7 +759,9 @@ async def __sync_force_change_locked_account( value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Unlock event diff --git a/batch/indexer_Position_Share.py b/batch/indexer_Position_Share.py index 4cb838ee3..5b031ba2e 100644 --- a/batch/indexer_Position_Share.py +++ b/batch/indexer_Position_Share.py @@ -414,7 +414,9 @@ async def __sync_lock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Lock event @@ -496,7 +498,9 @@ async def __sync_force_lock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Lock event @@ -580,7 +584,9 @@ async def __sync_unlock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Unlock event @@ -664,7 +670,9 @@ async def __sync_force_unlock(self, db_session: AsyncSession, block_to: int): value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Unlock event @@ -751,7 +759,9 @@ async def __sync_force_change_locked_account( value = args.get("value", 0) data = args.get("data", "") event_created = await self.__gen_block_timestamp(event=event) - tx = await async_web3.eth.get_transaction(event["transactionHash"]) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) msg_sender = tx["from"] # Index Unlock event diff --git a/batch/indexer_Transfer.py b/batch/indexer_Transfer.py index 445d0e49f..13e788baf 100644 --- a/batch/indexer_Transfer.py +++ b/batch/indexer_Transfer.py @@ -381,7 +381,9 @@ async def __sync_transfer( # Judge whether the transfer is a reallocation transaction_hash = event["transactionHash"].to_0x_hex() is_reallocation = False - tx = await async_web3.eth.get_transaction(transaction_hash) + tx = await AsyncContract.get_transaction( + event["transactionHash"], event["blockNumber"] + ) tx_data: HexBytes | None = tx.get("input") # Check if the transaction data contains the reallocation marker("c0ffee00") if "c0ffee00" in tx_data.hex(): diff --git a/tests/batch/indexer_Position_Bond_test.py b/tests/batch/indexer_Position_Bond_test.py index fa31ff68c..52c290b3e 100644 --- a/tests/batch/indexer_Position_Bond_test.py +++ b/tests/batch/indexer_Position_Bond_test.py @@ -30,7 +30,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from web3 import Web3 -from web3.exceptions import ABIEventNotFound +from web3.exceptions import ABIEventNotFound, TransactionNotFound from web3.middleware import ExtraDataToPOAMiddleware from app import config @@ -499,7 +499,8 @@ async def test_normal_3(self, processor, shared_contract, session): # Single Token # Single event logs # - Lock - async def test_normal_4_1(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_4_1(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -526,7 +527,14 @@ async def test_normal_4_1(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_list: Sequence[IDXPosition] = session.scalars( @@ -595,7 +603,8 @@ async def test_normal_4_1(self, processor, shared_contract, session): # Single Token # Single event logs # - ForceLock - async def test_normal_4_2(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_4_2(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -634,7 +643,14 @@ async def test_normal_4_2(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_issuer: IDXPosition = session.scalars( @@ -705,7 +721,8 @@ async def test_normal_4_2(self, processor, shared_contract, session): # Single Token # Single event logs # - Unlock - async def test_normal_5_1(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_5_1(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -734,7 +751,14 @@ async def test_normal_5_1(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_list: Sequence[IDXPosition] = session.scalars( @@ -825,7 +849,8 @@ async def test_normal_5_1(self, processor, shared_contract, session): # Single Token # Single event logs # - ForceUnlock - async def test_normal_5_2(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_5_2(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -855,7 +880,14 @@ async def test_normal_5_2(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_list: Sequence[IDXPosition] = session.scalars( @@ -946,7 +978,8 @@ async def test_normal_5_2(self, processor, shared_contract, session): # Single Token # Single event logs # - ForceChangeLockedAccount - async def test_normal_5_3(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_5_3(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -989,7 +1022,14 @@ async def test_normal_5_3(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_issuer: IDXPosition = session.scalars( diff --git a/tests/batch/indexer_Position_Share_test.py b/tests/batch/indexer_Position_Share_test.py index 9a0b1b037..34f74c3b5 100644 --- a/tests/batch/indexer_Position_Share_test.py +++ b/tests/batch/indexer_Position_Share_test.py @@ -30,7 +30,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from web3 import Web3 -from web3.exceptions import ABIEventNotFound +from web3.exceptions import ABIEventNotFound, TransactionNotFound from web3.middleware import ExtraDataToPOAMiddleware from app import config @@ -481,7 +481,8 @@ async def test_normal_3(self, processor, shared_contract, session): # Single Token # Single event logs # - Lock - async def test_normal_4_1(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_4_1(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -508,7 +509,14 @@ async def test_normal_4_1(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_list: Sequence[IDXPosition] = session.scalars( @@ -574,7 +582,8 @@ async def test_normal_4_1(self, processor, shared_contract, session): # Single Token # Single event logs # - ForceLock - async def test_normal_4_2(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_4_2(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -613,7 +622,14 @@ async def test_normal_4_2(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_issuer: IDXPosition = session.scalars( @@ -684,7 +700,8 @@ async def test_normal_4_2(self, processor, shared_contract, session): # Single Token # Single event logs # - Unlock - async def test_normal_5_1(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_5_1(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -713,7 +730,14 @@ async def test_normal_5_1(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_list: Sequence[IDXPosition] = session.scalars( @@ -800,7 +824,8 @@ async def test_normal_5_1(self, processor, shared_contract, session): # Single Token # Single event logs # - ForceUnlock - async def test_normal_5_2(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_5_2(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -830,7 +855,14 @@ async def test_normal_5_2(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_list: Sequence[IDXPosition] = session.scalars( @@ -917,7 +949,8 @@ async def test_normal_5_2(self, processor, shared_contract, session): # Single Token # Single event logs # - ForceChangeLockedAccount - async def test_normal_5_3(self, processor, shared_contract, session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_5_3(self, processor, shared_contract, session, is_old_token): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract = shared_contract["PersonalInfo"] @@ -959,7 +992,14 @@ async def test_normal_5_3(self, processor, shared_contract, session): # Run target process block_number = web3.eth.block_number - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion _position_issuer: IDXPosition = session.scalars( diff --git a/tests/batch/indexer_Transfer_test.py b/tests/batch/indexer_Transfer_test.py index ec286e0d1..a09522c06 100644 --- a/tests/batch/indexer_Transfer_test.py +++ b/tests/batch/indexer_Transfer_test.py @@ -28,7 +28,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession from web3 import Web3 -from web3.exceptions import ABIEventNotFound +from web3.exceptions import ABIEventNotFound, TransactionNotFound from web3.middleware import ExtraDataToPOAMiddleware from web3.types import RPCEndpoint @@ -236,7 +236,10 @@ async def listing_token(token_address, async_session): # - ForceUnlock # - ForceChangeLockedAccount # - Reallocation - async def test_normal_1_1(self, processor, shared_contract, async_session): + @pytest.mark.parametrize("is_old_token", [False, True]) + async def test_normal_1_1( + self, processor, shared_contract, async_session, is_old_token + ): # Issue Token token_list_contract = shared_contract["TokenList"] personal_info_contract_address = shared_contract["PersonalInfo"]["address"] @@ -325,7 +328,14 @@ async def test_normal_1_1(self, processor, shared_contract, async_session): block_number_5 = web3.eth.block_number # Execute batch processing - await processor.sync_new_logs() + if is_old_token: + with mock.patch( + "web3.eth.async_eth.AsyncEth.get_transaction", + MagicMock(side_effect=TransactionNotFound(message="")), + ): + await processor.sync_new_logs() + else: + await processor.sync_new_logs() # Assertion idx_transfer_list = ( From 6e2ff8ee0630c7332ea2f5f98c74b7659dfae788 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:38:23 +0900 Subject: [PATCH 15/22] Update hardhat to version 2.26.3 and adjust versioning in package files (#1672) * Update hardhat to version 2.26.3 and adjust versioning in package files * fix --- package-lock.json | 2 +- package.json | 2 +- tests/batch/indexer_Transfer_test.py | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 61d122633..a93a52e45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "hardhat": "^2.26.2" + "hardhat": "~2.26.2" } }, "node_modules/@ethereumjs/rlp": { diff --git a/package.json b/package.json index 9b4cb518e..ae2c88235 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "devDependencies": { - "hardhat": "^2.26.2" + "hardhat": "~2.26.2" } } diff --git a/tests/batch/indexer_Transfer_test.py b/tests/batch/indexer_Transfer_test.py index a09522c06..e825907a9 100644 --- a/tests/batch/indexer_Transfer_test.py +++ b/tests/batch/indexer_Transfer_test.py @@ -339,9 +339,7 @@ async def test_normal_1_1( # Assertion idx_transfer_list = ( - await async_session.scalars( - select(IDXTransfer).order_by(IDXTransfer.created) - ) + await async_session.scalars(select(IDXTransfer).order_by(IDXTransfer.id)) ).all() assert len(idx_transfer_list) == 5 From 53ff50ea6c91d3246aa7e1d01b06bb468091daa9 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:38:37 +0900 Subject: [PATCH 16/22] Add retry mechanism to HTTP requests in company_list/token_list/account_list indexer (#1675) * Add retry mechanism to HTTP requests in company_list/token_list/account_list indexer * Refactor HTTP requests in indexers to use session with retry mechanism * Refactor tests to use requests.Session for HTTP mocking --- batch/indexer_Company_List.py | 17 +++++++--- batch/indexer_PublicInfo_PublicAccountList.py | 15 ++++++-- batch/indexer_PublicInfo_TokenList.py | 17 +++++++--- tests/batch/indexer_Company_List_test.py | 34 +++++++++---------- ...dexer_PublicInfo_PublicAccountList_test.py | 18 +++++----- .../indexer_PublicInfo_TokenList_test.py | 20 +++++------ 6 files changed, 74 insertions(+), 47 deletions(-) diff --git a/batch/indexer_Company_List.py b/batch/indexer_Company_List.py index 7242240e5..3ab1dbaf1 100644 --- a/batch/indexer_Company_List.py +++ b/batch/indexer_Company_List.py @@ -24,10 +24,12 @@ import requests from eth_utils import to_checksum_address +from requests.adapters import HTTPAdapter from sqlalchemy import delete from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm.session import Session +from urllib3 import Retry from app.config import ( COMPANY_LIST_SLEEP_INTERVAL, @@ -56,10 +58,17 @@ def process(self): # Get from COMPANY_LIST_URL try: - _resp = requests.get(COMPANY_LIST_URL, timeout=REQUEST_TIMEOUT) - if _resp.status_code != 200: - raise Exception(f"status code={_resp.status_code}") - company_list_json = _resp.json() + with requests.Session() as session: + adapter = HTTPAdapter(max_retries=Retry(3, allowed_methods=["GET"])) + session.mount("http://", adapter) + session.mount("https://", adapter) + _resp = session.get( + url=COMPANY_LIST_URL, + timeout=REQUEST_TIMEOUT, + ) + if _resp.status_code != 200: + raise Exception(f"status code={_resp.status_code}") + company_list_json = _resp.json() except Exception: LOG.exception("Failed to get company list") return diff --git a/batch/indexer_PublicInfo_PublicAccountList.py b/batch/indexer_PublicInfo_PublicAccountList.py index 35b9148e1..b91580aff 100644 --- a/batch/indexer_PublicInfo_PublicAccountList.py +++ b/batch/indexer_PublicInfo_PublicAccountList.py @@ -24,10 +24,12 @@ import requests from eth_utils import to_checksum_address +from requests.adapters import HTTPAdapter from sqlalchemy import delete from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm.session import Session +from urllib3 import Retry from app.config import ( DATABASE_URL, @@ -56,9 +58,16 @@ def process(self): # Get data from PUBLIC_ACCOUNT_LIST_URL try: - _resp = requests.get(PUBLIC_ACCOUNT_LIST_URL, timeout=REQUEST_TIMEOUT) - if _resp.status_code != 200: - raise Exception(f"status code={_resp.status_code}") + with requests.Session() as session: + adapter = HTTPAdapter(max_retries=Retry(3, allowed_methods=["GET"])) + session.mount("http://", adapter) + session.mount("https://", adapter) + _resp = session.get( + url=PUBLIC_ACCOUNT_LIST_URL, + timeout=REQUEST_TIMEOUT, + ) + if _resp.status_code != 200: + raise Exception(f"status code={_resp.status_code}") account_list_json = _resp.json() except Exception: LOG.exception("Failed to get public account list") diff --git a/batch/indexer_PublicInfo_TokenList.py b/batch/indexer_PublicInfo_TokenList.py index 90051c7dc..6a62be199 100644 --- a/batch/indexer_PublicInfo_TokenList.py +++ b/batch/indexer_PublicInfo_TokenList.py @@ -24,10 +24,12 @@ import requests from eth_utils import to_checksum_address +from requests.adapters import HTTPAdapter from sqlalchemy import delete from sqlalchemy.engine.create import create_engine from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm.session import Session +from urllib3 import Retry from app.config import ( DATABASE_URL, @@ -56,10 +58,17 @@ def process(self): # Get from TOKEN_LIST_URL try: - _resp = requests.get(TOKEN_LIST_URL, timeout=REQUEST_TIMEOUT) - if _resp.status_code != 200: - raise Exception(f"status code={_resp.status_code}") - token_list_json = _resp.json() + with requests.Session() as session: + adapter = HTTPAdapter(max_retries=Retry(3, allowed_methods=["GET"])) + session.mount("http://", adapter) + session.mount("https://", adapter) + _resp = session.get( + url=TOKEN_LIST_URL, + timeout=REQUEST_TIMEOUT, + ) + if _resp.status_code != 200: + raise Exception(f"status code={_resp.status_code}") + token_list_json = _resp.json() except Exception: LOG.exception("Failed to get token list") return diff --git a/tests/batch/indexer_Company_List_test.py b/tests/batch/indexer_Company_List_test.py index 1b5479546..bac54d095 100644 --- a/tests/batch/indexer_Company_List_test.py +++ b/tests/batch/indexer_Company_List_test.py @@ -64,7 +64,7 @@ class TestProcessor: # # 0 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_1(self, mock_get, processor, session): # Prepare data _company = Company() @@ -102,7 +102,7 @@ async def test_normal_1(self, mock_get, processor, session): # # 1 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -157,7 +157,7 @@ async def test_normal_2(self, mock_get, processor, session): # # 2 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -226,7 +226,7 @@ async def test_normal_3(self, mock_get, processor, session): # Insert SKIP # type error # address - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_1_1(self, mock_get, processor, session): # Prepare data _company = Company() @@ -289,7 +289,7 @@ async def test_normal_4_1_1(self, mock_get, processor, session): # Insert SKIP # type error # corporate_name - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_1_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -352,7 +352,7 @@ async def test_normal_4_1_2(self, mock_get, processor, session): # Insert SKIP # type error # rsa_publickey - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_1_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -415,7 +415,7 @@ async def test_normal_4_1_3(self, mock_get, processor, session): # Insert SKIP # type error # homepage - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_1_4(self, mock_get, processor, session): # Prepare data _company = Company() @@ -478,7 +478,7 @@ async def test_normal_4_1_4(self, mock_get, processor, session): # Insert SKIP # required error # address - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_2_1(self, mock_get, processor, session): # Prepare data _company = Company() @@ -540,7 +540,7 @@ async def test_normal_4_2_1(self, mock_get, processor, session): # Insert SKIP # required error # corporate_name - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_2_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -602,7 +602,7 @@ async def test_normal_4_2_2(self, mock_get, processor, session): # Insert SKIP # required error # address - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_2_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -663,7 +663,7 @@ async def test_normal_4_2_3(self, mock_get, processor, session): # # Insert SKIP # invalid address error - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_3(self, mock_get, processor, session): # Prepare data _company = Company() @@ -725,7 +725,7 @@ async def test_normal_4_3(self, mock_get, processor, session): # # There are no differences from last time # -> Skip this cycle - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_5_1(self, mock_get, processor, session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -801,7 +801,7 @@ async def test_normal_5_1(self, mock_get, processor, session, caplog): # # There are differences from the previous cycle - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_5_2(self, mock_get, processor, session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -869,7 +869,7 @@ async def test_normal_5_2(self, mock_get, processor, session, caplog): # API error # Connection error @mock.patch( - "requests.get", + "requests.Session.get", MagicMock(side_effect=requests.exceptions.ConnectionError), ) async def test_error_1_1(self, processor, session): @@ -906,7 +906,7 @@ async def test_error_1_1(self, processor, session): # # API error # not succeed api - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_error_1_2(self, mock_get, processor, session): # Prepare data _company = Company() @@ -944,7 +944,7 @@ async def test_error_1_2(self, mock_get, processor, session): # # not decode response @mock.patch( - "requests.get", + "requests.Session.get", MagicMock(side_effect=json.decoder.JSONDecodeError), ) async def test_error_2(self, processor, session): @@ -980,7 +980,7 @@ async def test_error_2(self, processor, session): # # other error - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_error_3(self, mock_get, processor, session): # Prepare data _company = Company() diff --git a/tests/batch/indexer_PublicInfo_PublicAccountList_test.py b/tests/batch/indexer_PublicInfo_PublicAccountList_test.py index ef4c9f6fe..ad23253ca 100644 --- a/tests/batch/indexer_PublicInfo_PublicAccountList_test.py +++ b/tests/batch/indexer_PublicInfo_PublicAccountList_test.py @@ -72,7 +72,7 @@ class TestProcessor: # # 0 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_1(self, mock_get, processor, async_session): # Prepare data _account_list = PublicAccountList() @@ -99,7 +99,7 @@ async def test_normal_1(self, mock_get, processor, async_session): # # Multiple records - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_2(self, mock_get, processor, async_session): # Prepare data _account_list = PublicAccountList() @@ -163,7 +163,7 @@ async def test_normal_2(self, mock_get, processor, async_session): # # There are no differences from last time # -> Skip this cycle - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_3_1(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -244,7 +244,7 @@ async def test_normal_3_1(self, mock_get, processor, async_session, caplog): # # There are differences from last time - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_3_2(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -317,7 +317,7 @@ async def test_normal_3_2(self, mock_get, processor, async_session, caplog): # # API error: ConnectionError @mock.patch( - "requests.get", + "requests.Session.get", MagicMock(side_effect=requests.exceptions.ConnectionError), ) async def test_error_1_1(self, processor, async_session): @@ -356,7 +356,7 @@ async def test_error_1_1(self, processor, async_session): # # API error: Not succeed request - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_error_1_2(self, mock_get, processor, async_session): # Prepare data _account_list = PublicAccountList() @@ -397,7 +397,7 @@ async def test_error_1_2(self, mock_get, processor, async_session): # # API error: JSONDecodeError @mock.patch( - "requests.get", + "requests.Session.get", MagicMock(side_effect=json.decoder.JSONDecodeError), ) async def test_error_1_3(self, processor, async_session): @@ -437,7 +437,7 @@ async def test_error_1_3(self, processor, async_session): # # Invalid type error # -> Skip processing - @mock.patch("requests.get") + @mock.patch("requests.Session.get") @pytest.mark.parametrize( "invalid_record", [ @@ -522,7 +522,7 @@ async def test_error_2(self, mock_get, processor, async_session, invalid_record) # # Other error - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_error_3(self, mock_get, processor, async_session): # Mock mock_get.side_effect = [ diff --git a/tests/batch/indexer_PublicInfo_TokenList_test.py b/tests/batch/indexer_PublicInfo_TokenList_test.py index 93fedb88a..128b04b39 100644 --- a/tests/batch/indexer_PublicInfo_TokenList_test.py +++ b/tests/batch/indexer_PublicInfo_TokenList_test.py @@ -70,7 +70,7 @@ class TestProcessor: # # 0 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_1(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -110,7 +110,7 @@ async def test_normal_1(self, mock_get, processor, async_session): # # 1 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_2(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -165,7 +165,7 @@ async def test_normal_2(self, mock_get, processor, async_session): # # 2 record - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_3(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -231,7 +231,7 @@ async def test_normal_3(self, mock_get, processor, async_session): # # There are no differences from last time # -> Skip this cycle - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_1(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -302,7 +302,7 @@ async def test_normal_4_1(self, mock_get, processor, async_session, caplog): # # There are differences from the previous cycle - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_normal_4_2(self, mock_get, processor, async_session, caplog): # Run target process: 1st time mock_get.side_effect = [ @@ -368,7 +368,7 @@ async def test_normal_4_2(self, mock_get, processor, async_session, caplog): # # API error: ConnectionError @mock.patch( - "requests.get", + "requests.Session.get", MagicMock(side_effect=requests.exceptions.ConnectionError), ) async def test_error_1_1(self, processor, async_session): @@ -394,7 +394,7 @@ async def test_error_1_1(self, processor, async_session): # # API error: Not succeed request - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_error_1_2(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() @@ -422,7 +422,7 @@ async def test_error_1_2(self, mock_get, processor, async_session): # # API error: JSONDecodeError @mock.patch( - "requests.get", + "requests.Session.get", MagicMock(side_effect=json.decoder.JSONDecodeError), ) async def test_error_1_3(self, processor, async_session): @@ -449,7 +449,7 @@ async def test_error_1_3(self, processor, async_session): # # Invalid type error # -> Skip processing - @mock.patch("requests.get") + @mock.patch("requests.Session.get") @pytest.mark.parametrize( "invalid_record", [ @@ -554,7 +554,7 @@ async def test_error_2(self, mock_get, processor, async_session, invalid_record) # # Other error - @mock.patch("requests.get") + @mock.patch("requests.Session.get") async def test_error_3(self, mock_get, processor, async_session): # Prepare data _token_list_item = TokenList() From 2c7aa0de0415142721f3e85b9c595dc1f471daad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:04:12 +0900 Subject: [PATCH 17/22] Bump MishaKav/pytest-coverage-comment from 1.1.56 to 1.1.57 (#1676) Bumps [MishaKav/pytest-coverage-comment](https://github.com/mishakav/pytest-coverage-comment) from 1.1.56 to 1.1.57. - [Release notes](https://github.com/mishakav/pytest-coverage-comment/releases) - [Changelog](https://github.com/MishaKav/pytest-coverage-comment/blob/main/CHANGELOG.md) - [Commits](https://github.com/mishakav/pytest-coverage-comment/compare/9638e4b1448019aba40c4aaaa1ade87a9f211aa1...1bdbba85fb74a2fdc1ec66383acec1091610f82b) --- updated-dependencies: - dependency-name: MishaKav/pytest-coverage-comment dependency-version: 1.1.57 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6e734afe4..9c3b3ec9c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -90,7 +90,7 @@ jobs: run: jrm cov/pytest.xml "cov/pytest-*.xml" - name: Pytest coverage comment id: coverageComment - uses: MishaKav/pytest-coverage-comment@9638e4b1448019aba40c4aaaa1ade87a9f211aa1 # v1.1.56 + uses: MishaKav/pytest-coverage-comment@1bdbba85fb74a2fdc1ec66383acec1091610f82b # v1.1.57 with: report-only-changed-files: true pytest-xml-coverage-path: cov/coverage.xml From 593ab7249255e1b4322a79f1298ae7e28d38ef94 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:17:53 +0900 Subject: [PATCH 18/22] Bump dependencies: fastapi to 0.116.1, requests to 2.32.5, and urllib3 to 2.5.0 (#1677) * Bump dependencies: fastapi to 0.116.1, requests to 2.32.5, and urllib3 to 2.5.0 * Bump version to 25.9 in multiple files --- README.md | 2 +- README_JA.md | 2 +- app/__init__.py | 2 +- app/main.py | 2 +- docs/ibet_wallet_api.yaml | 2 +- pyproject.toml | 4 +-- uv.lock | 67 ++++++++++++++++++++------------------- 7 files changed, 41 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index b1dd2fd1d..74500addd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # ibet Wallet API

- Version + Version License: Apache--2.0

diff --git a/README_JA.md b/README_JA.md index 9e8b12f98..ef4ff5e09 100644 --- a/README_JA.md +++ b/README_JA.md @@ -5,7 +5,7 @@ # ibet Wallet API

- Version + Version License: Apache--2.0

diff --git a/app/__init__.py b/app/__init__.py index 37f369b24..f5ba527c7 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -17,4 +17,4 @@ SPDX-License-Identifier: Apache-2.0 """ -__version__ = "25.6.0" +__version__ = "25.9.0" diff --git a/app/main.py b/app/main.py index 4af4e117e..31dbb5e0d 100644 --- a/app/main.py +++ b/app/main.py @@ -119,7 +119,7 @@ async def lifespan(_: FastAPI): title="ibet Wallet API", description="RPC services that provides utility tools for building a wallet system on ibet network", terms_of_service="", - version="25.6.0", + version="25.9.0", contact={"email": "dev@boostry.co.jp"}, license_info={ "name": "Apache 2.0", diff --git a/docs/ibet_wallet_api.yaml b/docs/ibet_wallet_api.yaml index 6002ef2c3..6cfc6e784 100644 --- a/docs/ibet_wallet_api.yaml +++ b/docs/ibet_wallet_api.yaml @@ -3,7 +3,7 @@ info: title: ibet Wallet API description: RPC services that provides utility tools for building a wallet system on ibet network - version: 25.6.0 + version: 25.9.0 paths: /: get: diff --git a/pyproject.toml b/pyproject.toml index 948909290..eee164dc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ibet-Wallet-API" -version = "25.6" +version = "25.9" description = "RPC services that provides utility functions for building a wallet system on ibet-Network." authors = [ {name = "BOOSTRY Co., Ltd.", email = "dev@boostry.co.jp"}, @@ -12,7 +12,7 @@ dependencies = [ "coincurve~=21.0.0", "eth-keyfile~=0.9.1", "eth-utils~=5.2.0", - "fastapi~=0.115.0", + "fastapi~=0.116.1", "gunicorn~=23.0.0", "hexbytes~=1.3.0", "orjson~=3.10.3", diff --git a/uv.lock b/uv.lock index dfa26594c..734c7ebf0 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.13" +version = "3.12.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -24,25 +24,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" }, - { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" }, - { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" }, - { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" }, - { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" }, - { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" }, - { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" }, - { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" }, + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, ] [[package]] @@ -59,14 +59,15 @@ wheels = [ [[package]] name = "aiosignal" -version = "1.3.2" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frozenlist" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] [[package]] @@ -554,16 +555,16 @@ wheels = [ [[package]] name = "fastapi" -version = "0.115.12" +version = "0.116.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, ] [[package]] @@ -774,7 +775,7 @@ wheels = [ [[package]] name = "ibet-wallet-api" -version = "25.6" +version = "25.9" source = { virtual = "." } dependencies = [ { name = "aiomysql" }, @@ -850,7 +851,7 @@ requires-dist = [ { name = "coincurve", specifier = "~=21.0.0" }, { name = "eth-keyfile", specifier = "~=0.9.1" }, { name = "eth-utils", specifier = "~=5.2.0" }, - { name = "fastapi", specifier = "~=0.115.0" }, + { name = "fastapi", specifier = "~=0.116.1" }, { name = "gunicorn", specifier = "~=23.0.0" }, { name = "hexbytes", specifier = "~=1.3.0" }, { name = "httpx", specifier = "~=0.28.0" }, @@ -1893,7 +1894,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1901,9 +1902,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -2167,11 +2168,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] From f208c9550730e6ec09deb9ac622ee35b8b3b49d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:19:45 +0900 Subject: [PATCH 19/22] Bump hardhat from 2.26.2 to 2.26.3 (#1678) Bumps [hardhat](https://github.com/NomicFoundation/hardhat/tree/HEAD/v-next/hardhat) from 2.26.2 to 2.26.3. - [Release notes](https://github.com/NomicFoundation/hardhat/releases) - [Changelog](https://github.com/NomicFoundation/hardhat/blob/main/v-next/hardhat/CHANGELOG.md) - [Commits](https://github.com/NomicFoundation/hardhat/commits/hardhat@2.26.3/v-next/hardhat) --- updated-dependencies: - dependency-name: hardhat dependency-version: 2.26.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index a93a52e45..5f760c9fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "hardhat": "~2.26.2" + "hardhat": "~2.26.3" } }, "node_modules/@ethereumjs/rlp": { @@ -1615,9 +1615,9 @@ "license": "ISC" }, "node_modules/hardhat": { - "version": "2.26.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.26.2.tgz", - "integrity": "sha512-uIETdC1MAWSnu+48B+583r3b8JqHtkBGEiXoAPV5hcXYGkCW5Fnnpn7SIgPa8owhObm9oUqIqMXMJ6H92N7Pyg==", + "version": "2.26.3", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.26.3.tgz", + "integrity": "sha512-gBfjbxCCEaRgMCRgTpjo1CEoJwqNPhyGMMVHYZJxoQ3LLftp2erSVf8ZF6hTQC0r2wst4NcqNmLWqMnHg1quTw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ae2c88235..37fc4f158 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "devDependencies": { - "hardhat": "~2.26.2" + "hardhat": "~2.26.3" } } From 000eb4c3b79b8d5d43cb5c05ce469e60746bc5ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:33:34 +0900 Subject: [PATCH 20/22] Bump actions/setup-node from 4 to 5 (#1680) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9c3b3ec9c..704f902c7 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -81,7 +81,7 @@ jobs: pattern: 'pytest-*' path: cov/ merge-multiple: true - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 20 - name: Install junit-report-merger From 436477b2af0afe177ca36245c10ec7d84eaa1320 Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Tue, 9 Sep 2025 10:25:23 +0900 Subject: [PATCH 21/22] Cherrypick 1682 (v25.9) (#1683) * Enhance public info models to include IbetMembership and IbetCoupon token types (#1682) * Update ibet_wallet_api.yaml to version 25.9.0 and enhance notification schemas --- app/model/db/public_info.py | 6 +- app/model/schema/public_info.py | 18 +- bin/run_indexer.sh | 13 ++ docs/ibet_wallet_api.yaml | 54 +++++ ...lic_info_ListAllPublicListedTokens_test.py | 198 +++++++++++++++++- 5 files changed, 276 insertions(+), 13 deletions(-) diff --git a/app/model/db/public_info.py b/app/model/db/public_info.py index 4871f8901..c826793be 100644 --- a/app/model/db/public_info.py +++ b/app/model/db/public_info.py @@ -43,9 +43,9 @@ class TokenList(Base): # Token Address token_address: Mapped[str] = mapped_column(String(42), primary_key=True) # Token Template - token_template: Mapped[Literal["ibetBond", "ibetShare"]] = mapped_column( - String(50), nullable=False - ) + token_template: Mapped[ + Literal["ibetBond", "ibetShare", "ibetMembership", "ibetCoupon"] + ] = mapped_column(String(50), nullable=False) # Key Manager key_manager: Mapped[list[str]] = mapped_column(JSON, nullable=False) # Product Type diff --git a/app/model/schema/public_info.py b/app/model/schema/public_info.py index 8f2dcbd8c..97c472151 100644 --- a/app/model/schema/public_info.py +++ b/app/model/schema/public_info.py @@ -48,6 +48,16 @@ class IbetShareToken(TokenBase): product_type: Literal[1, 2, 3, 4, 5] +class IbetMembershipToken(TokenBase): + token_template: Literal["ibetMembership"] + product_type: Literal[1] + + +class IbetCouponToken(TokenBase): + token_template: Literal["ibetCoupon"] + product_type: Literal[1] + + class PublicAccount(BaseModel): key_manager: str key_manager_name: str @@ -64,8 +74,8 @@ class ListAllPublicListedTokensSortItem(StrEnum): class ListAllPublicListedTokensQuery(BasePaginationQuery): - token_template: Literal["ibetBond", "ibetShare"] = Field( - None, description="Token template" + token_template: Literal["ibetBond", "ibetShare", "ibetMembership", "ibetCoupon"] = ( + Field(None, description="Token template") ) sort_item: ListAllPublicListedTokensSortItem = Field( default=ListAllPublicListedTokensSortItem.token_address, description="sort item" @@ -97,7 +107,9 @@ class ListAllPublicAccountsQuery(BasePaginationQuery): ############################ class ListAllPublicListedTokensResponse(BaseModel): result_set: ResultSet - tokens: list[Union[IbetBondToken, IbetShareToken]] + tokens: list[ + Union[IbetBondToken, IbetShareToken, IbetMembershipToken, IbetCouponToken] + ] class ListAllPublicAccountsResponse(BaseModel): diff --git a/bin/run_indexer.sh b/bin/run_indexer.sh index cd2093957..661f7a14a 100755 --- a/bin/run_indexer.sh +++ b/bin/run_indexer.sh @@ -58,6 +58,19 @@ if [ "${resp}" -ne 200 ]; then echo "please confirm TOKEN_LIST_URL, which response code is ${resp}" >&2 fi python batch/indexer_PublicInfo_TokenList.py & + + +# check PUBLIC_ACCOUNT_LIST_URL +if [ -z "${PUBLIC_ACCOUNT_LIST_URL:-}" ]; then + echo -n '[ERROR] Please ensure that the environment variable PUBLIC_ACCOUNT_LIST_URL is set.' >&2 + exit 1 +fi +# check PUBLIC_ACCOUNT_LIST_URL available +resp=$(curl "${PUBLIC_ACCOUNT_LIST_URL}" -o /dev/null -w '%{http_code}\n' -s) +if [ "${resp}" -ne 200 ]; then + echo -n "[WARNING] Could not access to PUBLIC_ACCOUNT_LIST_URL, " >&2 + echo "please confirm PUBLIC_ACCOUNT_LIST_URL, which response code is ${resp}" >&2 +fi python batch/indexer_PublicInfo_PublicAccountList.py & diff --git a/docs/ibet_wallet_api.yaml b/docs/ibet_wallet_api.yaml index 6cfc6e784..b3ae2669a 100644 --- a/docs/ibet_wallet_api.yaml +++ b/docs/ibet_wallet_api.yaml @@ -52,6 +52,8 @@ paths: enum: - ibetBond - ibetShare + - ibetMembership + - ibetCoupon type: string description: Token template title: Token Template @@ -8297,6 +8299,31 @@ components: - token_template - product_type title: IbetBondToken + IbetCouponToken: + properties: + token_address: + type: string + title: Token Address + key_manager: + items: + type: string + type: array + title: Key Manager + token_template: + type: string + const: ibetCoupon + title: Token Template + product_type: + type: integer + const: 1 + title: Product Type + type: object + required: + - token_address + - key_manager + - token_template + - product_type + title: IbetCouponToken IbetEscrowEventType: type: string enum: @@ -8306,6 +8333,31 @@ components: - EscrowCanceled - EscrowFinished title: IbetEscrowEventType + IbetMembershipToken: + properties: + token_address: + type: string + title: Token Address + key_manager: + items: + type: string + type: array + title: Key Manager + token_template: + type: string + const: ibetMembership + title: Token Template + product_type: + type: integer + const: 1 + title: Product Type + type: object + required: + - token_address + - key_manager + - token_template + - product_type + title: IbetMembershipToken IbetSecurityTokenDVPEventType: type: string enum: @@ -8724,6 +8776,8 @@ components: anyOf: - $ref: '#/components/schemas/IbetBondToken' - $ref: '#/components/schemas/IbetShareToken' + - $ref: '#/components/schemas/IbetMembershipToken' + - $ref: '#/components/schemas/IbetCouponToken' type: array title: Tokens type: object diff --git a/tests/app/public_info_ListAllPublicListedTokens_test.py b/tests/app/public_info_ListAllPublicListedTokens_test.py index f761b2c14..8ad5bbad7 100644 --- a/tests/app/public_info_ListAllPublicListedTokens_test.py +++ b/tests/app/public_info_ListAllPublicListedTokens_test.py @@ -78,6 +78,20 @@ def test_normal_2(self, client: TestClient, session: Session): _token_list_item.product_type = 5 session.add(_token_list_item) + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_4 + _token_list_item.token_template = "ibetMembership" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_5 + _token_list_item.token_template = "ibetCoupon" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + session.commit() resp = client.get(self.api_url, params={}) @@ -85,7 +99,7 @@ def test_normal_2(self, client: TestClient, session: Session): assert resp.status_code == 200 assert resp.json()["meta"] == {"code": 200, "message": "OK"} assert resp.json()["data"] == { - "result_set": {"count": 3, "limit": None, "offset": None, "total": 3}, + "result_set": {"count": 5, "limit": None, "offset": None, "total": 5}, "tokens": [ { "key_manager": ["0000000000000"], @@ -105,6 +119,18 @@ def test_normal_2(self, client: TestClient, session: Session): "token_address": self.token_address_3, "token_template": "ibetShare", }, + { + "key_manager": [], + "product_type": 1, + "token_address": self.token_address_4, + "token_template": "ibetMembership", + }, + { + "key_manager": [], + "product_type": 1, + "token_address": self.token_address_5, + "token_template": "ibetCoupon", + }, ], } @@ -122,18 +148,35 @@ def test_normal_3_1( _token_list_item.key_manager = ["0000000000000"] _token_list_item.product_type = 1 session.add(_token_list_item) + _token_list_item = TokenList() _token_list_item.token_address = self.token_address_2 _token_list_item.token_template = "ibetBond" _token_list_item.key_manager = ["0000000000000"] _token_list_item.product_type = 1 session.add(_token_list_item) + _token_list_item = TokenList() _token_list_item.token_address = self.token_address_3 _token_list_item.token_template = "ibetShare" _token_list_item.key_manager = ["1111111111111"] _token_list_item.product_type = 5 session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_4 + _token_list_item.token_template = "ibetMembership" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_5 + _token_list_item.token_template = "ibetCoupon" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + session.commit() resp = client.get(self.api_url, params={"token_template": "ibetBond"}) @@ -141,7 +184,7 @@ def test_normal_3_1( assert resp.status_code == 200 assert resp.json()["meta"] == {"code": 200, "message": "OK"} assert resp.json()["data"] == { - "result_set": {"count": 2, "limit": None, "offset": None, "total": 3}, + "result_set": {"count": 2, "limit": None, "offset": None, "total": 5}, "tokens": [ { "key_manager": ["0000000000000"], @@ -172,18 +215,35 @@ def test_normal_3_2( _token_list_item.key_manager = ["0000000000000"] _token_list_item.product_type = 1 session.add(_token_list_item) + _token_list_item = TokenList() _token_list_item.token_address = self.token_address_2 _token_list_item.token_template = "ibetBond" _token_list_item.key_manager = ["0000000000000"] _token_list_item.product_type = 1 session.add(_token_list_item) + _token_list_item = TokenList() _token_list_item.token_address = self.token_address_3 _token_list_item.token_template = "ibetShare" _token_list_item.key_manager = ["1111111111111"] _token_list_item.product_type = 5 session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_4 + _token_list_item.token_template = "ibetMembership" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_5 + _token_list_item.token_template = "ibetCoupon" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + session.commit() resp = client.get(self.api_url, params={"token_template": "ibetShare"}) @@ -191,7 +251,7 @@ def test_normal_3_2( assert resp.status_code == 200 assert resp.json()["meta"] == {"code": 200, "message": "OK"} assert resp.json()["data"] == { - "result_set": {"count": 1, "limit": None, "offset": None, "total": 3}, + "result_set": {"count": 1, "limit": None, "offset": None, "total": 5}, "tokens": [ { "key_manager": ["1111111111111"], @@ -202,6 +262,128 @@ def test_normal_3_2( ], } + # <Normal_3_3> + # Filter: token_template (ibetMembership) + def test_normal_3_3( + self, + client: TestClient, + session: Session, + ): + # Prepare data + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_1 + _token_list_item.token_template = "ibetBond" + _token_list_item.key_manager = ["0000000000000"] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_2 + _token_list_item.token_template = "ibetBond" + _token_list_item.key_manager = ["0000000000000"] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_3 + _token_list_item.token_template = "ibetShare" + _token_list_item.key_manager = ["1111111111111"] + _token_list_item.product_type = 5 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_4 + _token_list_item.token_template = "ibetMembership" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_5 + _token_list_item.token_template = "ibetCoupon" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + session.commit() + + resp = client.get(self.api_url, params={"token_template": "ibetMembership"}) + + assert resp.status_code == 200 + assert resp.json()["meta"] == {"code": 200, "message": "OK"} + assert resp.json()["data"] == { + "result_set": {"count": 1, "limit": None, "offset": None, "total": 5}, + "tokens": [ + { + "key_manager": [], + "product_type": 1, + "token_address": self.token_address_4, + "token_template": "ibetMembership", + }, + ], + } + + # <Normal_3_4> + # Filter: token_template (ibetCoupon) + def test_normal_3_4( + self, + client: TestClient, + session: Session, + ): + # Prepare data + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_1 + _token_list_item.token_template = "ibetBond" + _token_list_item.key_manager = ["0000000000000"] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_2 + _token_list_item.token_template = "ibetBond" + _token_list_item.key_manager = ["0000000000000"] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_3 + _token_list_item.token_template = "ibetShare" + _token_list_item.key_manager = ["1111111111111"] + _token_list_item.product_type = 5 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_4 + _token_list_item.token_template = "ibetMembership" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + _token_list_item = TokenList() + _token_list_item.token_address = self.token_address_5 + _token_list_item.token_template = "ibetCoupon" + _token_list_item.key_manager = [] + _token_list_item.product_type = 1 + session.add(_token_list_item) + + session.commit() + + resp = client.get(self.api_url, params={"token_template": "ibetCoupon"}) + + assert resp.status_code == 200 + assert resp.json()["meta"] == {"code": 200, "message": "OK"} + assert resp.json()["data"] == { + "result_set": {"count": 1, "limit": None, "offset": None, "total": 5}, + "tokens": [ + { + "key_manager": [], + "product_type": 1, + "token_address": self.token_address_5, + "token_template": "ibetCoupon", + }, + ], + } + # <Normal_4> # Offset def test_normal_4( @@ -306,11 +488,13 @@ def test_error_1(self, client: TestClient, session: Session): "message": "Invalid Parameter", "description": [ { - "ctx": {"expected": "'ibetBond' or 'ibetShare'"}, - "input": "invalid_value", - "loc": ["query", "token_template"], - "msg": "Input should be 'ibetBond' or 'ibetShare'", "type": "literal_error", + "loc": ["query", "token_template"], + "msg": "Input should be 'ibetBond', 'ibetShare', 'ibetMembership' or 'ibetCoupon'", + "input": "invalid_value", + "ctx": { + "expected": "'ibetBond', 'ibetShare', 'ibetMembership' or 'ibetCoupon'" + }, } ], } From 076e7dadef82840694a7f6bdde05dc0d5c5b33e1 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu <15183665+purplesmoke05@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:24:55 +0900 Subject: [PATCH 22/22] Bump starlette from 0.46.2 to 0.47.3 and update revision in uv.lock (#1685) --- uv.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 734c7ebf0..e3b78db5d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = "==3.12.9" [[package]] @@ -2062,14 +2062,15 @@ asyncio = [ [[package]] name = "starlette" -version = "0.46.2" +version = "0.47.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/b9/cc3017f9a9c9b6e27c5106cc10cc7904653c3eec0729793aec10479dd669/starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9", size = 2584144, upload-time = "2025-08-24T13:36:42.122Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fd/901cfa59aaa5b30a99e16876f11abe38b59a1a2c51ffb3d7142bb6089069/starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51", size = 72991, upload-time = "2025-08-24T13:36:40.887Z" }, ] [[package]]