diff --git a/.circleci/config.yml b/.circleci/config.yml index 42763c9c3..475588245 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ defaults: &defaults machine_ubuntu: &machine_ubuntu working_directory: ~/circleci-caver-java-major machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202010-01 android_machine_ubuntu: &android_machine_ubuntu working_directory: ~/circleci-caver-java-android @@ -195,9 +195,6 @@ jobs: command: ./gradlew connectedDebugAndroidTest --debug --stacktrace - store_test_results: path: android_instrumented_test/build/outputs/androidTest-results/connected - - store_artifacts: - path: ~/circleci-caver-java-android/ - destination: adb.log build_test_android: <<: *android_machine_ubuntu diff --git a/.circleci/images/docker-compose.yml b/.circleci/images/docker-compose.yml index 6b5c9bca9..145f8e423 100755 --- a/.circleci/images/docker-compose.yml +++ b/.circleci/images/docker-compose.yml @@ -16,11 +16,11 @@ services: - | git clone https://github.com/klaytn/klaytn.git cd klaytn - git checkout master + git checkout dev make kcn export PATH=$PATH:`pwd`/build/bin mkdir -p /klaytn - echo '{"config":{"chainId":2019,"istanbul":{"epoch":604800,"policy":2,"sub":13},"unitPrice":25000000000,"deriveShaImpl":2,"governance":{"governingNode":"0x73718c4980728857f3aa5148e9d1b471efa3a7dd","governanceMode":"single","reward":{"mintingAmount":9600000000000000000,"ratio":"34/54/12","useGiniCoeff":false,"deferredTxFee":true,"stakingUpdateInterval":86400,"proposerUpdateInterval":3600,"minimumStake":5000000}}},"timestamp":"0x5cef3d41","extraData":"0x0000000000000000000000000000000000000000000000000000000000000000f85ad59473718c4980728857f3aa5148e9d1b471efa3a7ddb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","governanceData":null,"blockScore":"0x1","alloc":{"0000000000000000000000000000000000000400":{"code":"0x6080604052600436106102215763ffffffff60e060020a60003504166315575d5a8114610226578063160370b8146102725780631865c57d1461037c57806321ac4ad4146103ec57806327e1f7df14610412578063298b3c611461043357806332b91e8514610460578063394a144a1461048b5780633f0628b1146104ac578063407091eb146104d057806341b6945c1461056c5780634a8c1fb4146105815780634b6a94cc146105aa5780634c5d435c146106345780634f97638f1461065857806350a5bb691461066d578063579740db1461068257806358d65880146106a35780636abd623d146106ca57806370480275146106fb578063715b208b1461071c57806376674c54146107ca578063778f39cb146107df5780637894c366146107f4578063791b51231461081857806382d67e5a14610839578063832a2aad146108d5578063863f5c0a146108f657806387cd9feb146109175780638e6f6b771461092c5780639258d76814610941578063934d1fa414610965578063afaaf3301461097a578063b50060e41461099b578063b5067706146109b0578063b7563930146109d1578063b858dd95146109e6578063c47afb3a146109fb578063c7e9de7514610a13578063cc11efc014610a37578063cec9246614610a64578063d267eda514610a79578063da34a0bd14610a8e578063de5bbfbc14610af3578063e748357b14610b08578063feb15ca114610b20578063ffa1ad7414610b35575b600080fd5b34801561023257600080fd5b50610247600160a060020a0360043516610b4a565b60408051600160a060020a039485168152928416602084015292168183015290519081900360600190f35b34801561027e57600080fd5b50610287610ca0565b60408051600160a060020a0380851660608301528316608082015260a080825287519082015286519091829160208084019284019160c08501918b8101910280838360005b838110156102e45781810151838201526020016102cc565b50505050905001848103835288818151815260200191508051906020019060200280838360005b8381101561032357818101518382015260200161030b565b50505050905001848103825287818151815260200191508051906020019060200280838360005b8381101561036257818101518382015260200161034a565b505050509050019850505050505050505060405180910390f35b34801561038857600080fd5b50610391610deb565b6040518080602001838152602001828103825284818151815260200191508051906020019060200280838360005b838110156103d75781810151838201526020016103bf565b50505050905001935050505060405180910390f35b3480156103f857600080fd5b50610410600160a060020a0360043516602435610e57565b005b34801561041e57600080fd5b50610410600160a060020a0360043516611038565b34801561043f57600080fd5b50610410600160a060020a03600435811690602435811690604435166112c9565b34801561046c57600080fd5b50610475611720565b6040805160ff9092168252519081900360200190f35b34801561049757600080fd5b50610410600160a060020a0360043516611725565b3480156104b857600080fd5b5061041060ff600435166024356044356064356117f6565b3480156104dc57600080fd5b506104f460ff60043516602435604435606435611e88565b60408051858152908101839052602081016060820183600481111561051557fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360005b8381101561055557818101518382015260200161053d565b505050509050019550505050505060405180910390f35b34801561057857600080fd5b50610475611f35565b34801561058d57600080fd5b50610596611f3a565b604080519115158252519081900360200190f35b3480156105b657600080fd5b506105bf611f43565b6040805160208082528351818301528351919283929083019185019080838360005b838110156105f95781810151838201526020016105e1565b50505050905090810190601f1680156106265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561064057600080fd5b50610410600160a060020a0360043516602435611f7a565b34801561066457600080fd5b5061041061216f565b34801561067957600080fd5b50610596612274565b34801561068e57600080fd5b50610410600160a060020a0360043516612282565b3480156106af57600080fd5b506106b8612658565b60408051918252519081900360200190f35b3480156106d657600080fd5b506106df61265d565b60408051600160a060020a039092168252519081900360200190f35b34801561070757600080fd5b50610410600160a060020a036004351661266c565b34801561072857600080fd5b5061073161284b565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561077557818101518382015260200161075d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b838110156107b457818101518382015260200161079c565b5050505090500194505050505060405180910390f35b3480156107d657600080fd5b50610475612b3a565b3480156107eb57600080fd5b506106b8612b3f565b34801561080057600080fd5b50610410602460048035828101929101359035612b44565b34801561082457600080fd5b50610410600160a060020a0360043516612ef1565b34801561084557600080fd5b506108516004356130f6565b6040518088600a81111561086157fe5b60ff16815260208101889052604081018790526060810186905260a08101849052608081019060c00183600481111561089657fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360008381101561036257818101518382015260200161034a565b3480156108e157600080fd5b50610410600160a060020a03600435166131b4565b34801561090257600080fd5b50610410600160a060020a03600435166133bd565b34801561092357600080fd5b5061041061358b565b34801561093857600080fd5b506106b8613649565b34801561094d57600080fd5b50610410600160a060020a0360043516602435613650565b34801561097157600080fd5b506106b86137e8565b34801561098657600080fd5b50610410600160a060020a03600435166137ef565b3480156109a757600080fd5b5061047561388e565b3480156109bc57600080fd5b50610410600160a060020a0360043516613893565b3480156109dd57600080fd5b506106b8613a49565b3480156109f257600080fd5b506106df613a4f565b348015610a0757600080fd5b50610410600435613a5e565b348015610a1f57600080fd5b50610410600160a060020a0360043516602435613bb1565b348015610a4357600080fd5b50610410600160a060020a0360043581169060243581169060443516613da6565b348015610a7057600080fd5b50610410614265565b348015610a8557600080fd5b506106df6145b5565b348015610a9a57600080fd5b50610aa36145c4565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610adf578181015183820152602001610ac7565b505050509050019250505060405180910390f35b348015610aff57600080fd5b5061047561461e565b348015610b1457600080fd5b50610410600435614623565b348015610b2c57600080fd5b506104106147a6565b348015610b4157600080fd5b506106b861461e565b600080808084600160a060020a0381161515610b9e576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600160a060020a038616600081815260086020526040902054600980549194509084908110610bc957fe5b600091825260209091200154600160a060020a031614610c21576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009805483908110610c2f57fe5b600091825260209091200154600a8054600160a060020a039092169184908110610c5557fe5b600091825260209091200154600b8054600160a060020a039092169185908110610c7b57fe5b6000918252602090912001549196509450600160a060020a0316925050509193909250565b60055460065460098054604080516020808402820181019092528281526060958695869560009586959194600a94600b94600160a060020a03918216949116928791830182828015610d1b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610cfd575b5050505050945083805480602002602001604051908101604052809291908181526020018280548015610d7757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610d59575b5050505050935082805480602002602001604051908101604052809291908181526020018280548015610dd357602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610db5575b50505050509250945094509450945094509091929394565b606060008060015481805480602002602001604051908101604052809291908181526020018280548015610e4857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610e2a575b50505050509150915091509091565b600082600160a060020a0381161515610ea8576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515610eff576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610f3e57600080fd5b505af1158015610f52573d6000803e3d6000fd5b505050506040513d6020811015610f6857600080fd5b505114610fbf576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b610fd66006600160a060020a038716866000614af1565b9250610fef6006600160a060020a038716866000614bc4565b610ff8836151f5565b80156110235750600160008481526003602052604090206006015460ff16600481111561102157fe5b145b156110315761103183615210565b5050505050565b600080333014611080576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff1615156110e2576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6000546110f690600163ffffffff61591416565b6001546032821115801561110a5750818111155b801561111557508015155b801561112057508115155b1515611164576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b60008054600160a060020a0388168252600260205260408220805460ff19169055955093505b6001850384101561123d5785600160a060020a03166000858154811015156111ae57fe5b600091825260209091200154600160a060020a03161415611232576000805460001987019081106111db57fe5b60009182526020822001548154600160a060020a039091169190869081106111ff57fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a0316021790555061123d565b60019093019261118a565b60008054600019870190811061124f57fe5b600091825260208220018054600160a060020a03191690555461127990600163ffffffff61591416565b611284600082615a01565b5061128d61216f565b604051600160a060020a038716907f1af6bd3d85a56e7c4a0700756fd2ca3b3b65c266e56c77652c5a346bc256522090600090a2505050505050565b6000333014611310576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600954600010156113ae57600160a060020a03841660008181526008602052604090205460098054909190811061134357fe5b600091825260209091200154600160a060020a031614156113ae576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b83600160a060020a031683600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156113f657600080fd5b505af115801561140a573d6000803e3d6000fd5b505050506040513d602081101561142057600080fd5b5051600160a060020a03161461146e576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b81600160a060020a031683600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156114b657600080fd5b505af11580156114ca573d6000803e3d6000fd5b505050506040513d60208110156114e057600080fd5b5051600160a060020a031614611540576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b82600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561157e57600080fd5b505af1158015611592573d6000803e3d6000fd5b505050506040513d60208110156115a857600080fd5b50511515600114611603576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b5060098054600160a060020a03808616600081815260086020908152604080832086905560018087019097557f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af86018054600160a060020a03199081168617909155600a8054808a019091557fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80180548b88169083168117909155600b8054998a0181559094527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db99097018054958916959097168517909655855192835282015280840191909152915190917fe01726557c1ea9f7286dca4bba890e96fea9041689db298806306cafa74c9e91919081900360600190a150505050565b600281565b3360008181526002602052604081205490919060ff16151561177f576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b611796600a600160a060020a038516600080614af1565b91506117af600a600160a060020a038516600080614bc4565b6117b8826151f5565b80156117e35750600160008381526003602052604090206006015460ff1660048111156117e157fe5b145b156117f1576117f182615210565b505050565b33600081815260026020526040812054909182918291829160ff161515611855576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b61186189898989614af1565b60008181526003602052604090206005015490955015156118cc576040805160e560020a62461bcd02815260206004820152601060248201527f496e76616c696420726571756573742e00000000000000000000000000000000604482015290519081900360640190fd5b600160008681526003602052604090206006015460ff1660048111156118ee57fe5b14611943576040805160e560020a62461bcd02815260206004820152601f60248201527f4d757374206265206174206e6f742d636f6e6669726d65642073746174652e00604482015290519081900360640190fd5b60008581526003602052604081206004015490945092508391505b82821015611e2657600085815260036020526040902060040180548390811061198357fe5b600091825260209091200154600160a060020a0316331415611e1b57600085815260036020526040902060050154600194504262093a809091011015611b2f5760008581526003602052604090206005015442621275009091011015611a3e576119ec8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611a266004830182615a25565b5060006005820155600601805460ff19169055611a5b565b6000858152600360205260409020600601805460ff191660041790555b600085815260036020526040908190209051339187917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918d918d918d918d91600401908086600a811115611aac57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611b1857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611afa575b5050965050505050505060405180910390a3611e16565b60001983018214611bc057600085815260036020526040902060040180546000198501908110611b5b57fe5b6000918252602080832090910154878352600390915260409091206004018054600160a060020a039092169184908110611b9157fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055505b600085815260036020526040902060040180546000198501908110611be157fe5b600091825260208083209091018054600160a060020a0319169055868252600390526040902060040154611c1c90600163ffffffff61591416565b6000868152600360205260409020611c379060040182615a01565b5060008581526003602081905260409182902080546001820154600283015493830154945133958b957f9c174b2536ba49e3478ca649dac74d9e9f71f70adf70f193e780eabbfcdc367c9560ff9095169490926004909101908086600a811115611c9d57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611d0957602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611ceb575b5050965050505050505060405180910390a36000858152600360205260409020600401541515611e1657611d3c8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611d766004830182615a25565b506000600582018190556006909101805460ff19169055858152600360208190526040918290208054600182015460028301549290930154935133948a947fbfda049a0206fd9c90ed4a3170f5bfaad83c323a16835dd68fea92faa247c2cd9460ff9094169390929091908085600a811115611dee57fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a35b611e26565b60019091019061195e565b831515611e7d576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220686173206e6f74207265717565737465642e000000604482015290519081900360640190fd5b505050505050505050565b600060606000806000611e9d89898989614af1565b600081815260036020908152604091829020600581015460068201546004909201805485518186028101860190965280865295965086959094919360ff90931692859190830182828015611f1a57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611efc575b50505050509250945094509450945050945094509450949050565b600481565b600c5460ff1681565b60408051808201909152600b81527f41646472657373426f6f6b000000000000000000000000000000000000000000602082015281565b600080333014611fc2576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561200157600080fd5b505af1158015612015573d6000803e3d6000fd5b505050506040513d602081101561202b57600080fd5b505114612082576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060068054600160a060020a03848116600160a060020a031983161790925516600081156121185781600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156120e957600080fd5b505af11580156120fd573d6000803e3d6000fd5b505050506040513d602081101561211357600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517ffdccdf242038c2d09605009fbb95e03f75cdbd106d0a9e52a1670be9553c88489181900360800190a150505050565b6000803330146121b7576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b505060045460005b8181101561223b57600360006004838154811015156121da57fe5b600091825260208083209091015483528201929092526040018120805460ff19168155600181018290556002810182905560038101829055906122206004830182615a25565b5060006005820155600601805460ff191690556001016121bf565b61224760046000615a25565b6040517f907527d30089abd16e30f06ddbbbc18480505176262f19bc16c1fbf9262f9c6b90600090a15050565b600c54610100900460ff1681565b60003330146122c9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b50600160a060020a03811660008181526008602052604090205460098054919291839081106122f457fe5b600091825260209091200154600160a060020a03161461234c576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009546001106123a6576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b6009546000190181101561251e576009805460001981019081106123c657fe5b60009182526020909120015460098054600160a060020a0390921691839081106123ec57fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600a805490916000190190811061242b57fe5b600091825260209091200154600a8054600160a060020a03909216918390811061245157fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600b805490916000190190811061249057fe5b600091825260209091200154600b8054600160a060020a0390921691839081106124b657fe5b600091825260208220018054600160a060020a031916600160a060020a039390931692909217909155600980548392600892909160001981019081106124f857fe5b6000918252602080832090910154600160a060020a031683528201929092526040019020555b600160a060020a03821660009081526008602052604081205560098054600019810190811061254957fe5b60009182526020909120018054600160a060020a031916905560095461257690600163ffffffff61591416565b612581600982615a01565b50600a8054600019810190811061259457fe5b60009182526020909120018054600160a060020a0319169055600a546125c190600163ffffffff61591416565b6125cc600a82615a01565b50600b805460001981019081106125df57fe5b60009182526020909120018054600160a060020a0319169055600b5461260c90600163ffffffff61591416565b612617600b82615a01565b5060408051600160a060020a038416815290517fa30079721e55931e89e7cdb421712ad0fcc817e7cac8fe954aa7ed0d46b9c42d9181900360200190a15050565b603281565b600754600160a060020a031681565b3330146126b1576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038116600090815260026020526040902054819060ff1615612724576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b60005461273890600163ffffffff6159e816565b6001546032821115801561274c5750818111155b801561275757508015155b801561276257508115155b15156127a6576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600160a060020a0384166000818152600260205260408120805460ff19166001908117909155815490810182559080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563018054600160a060020a031916909117905561281161216f565b604051600160a060020a038516907fad6de4452a631e641cb59902236607946ce9272b9b981f2f80e8d129cb9084ba90600090a250505050565b600c54606090819081908190600090819060ff161515612884576040805160008082526020820190815281830190925294509250612b2f565b6009805490506003026002016040519080825280602002602001820160405280156128b9578160200160208202803883390190505b5093506009805490506003026002016040519080825280602002602001820160405280156128f1578160200160208202803883390190505b506009549093509150600090505b81811015612a71576000848260030281518110151561291a57fe5b60ff909216602092830290910190910152600980548290811061293957fe5b6000918252602090912001548351600160a060020a039091169084906003840290811061296257fe5b600160a060020a039092166020928302909101909101528351600190859060038402830190811061298f57fe5b60ff909216602092830290910190910152600a8054829081106129ae57fe5b6000918252602090912001548351600160a060020a03909116908490600384026001019081106129da57fe5b600160a060020a0390921660209283029091019091015283516002908590600384028301908110612a0757fe5b60ff909216602092830290910190910152600b805482908110612a2657fe5b6000918252602090912001548351600160a060020a0390911690849060026003850201908110612a5257fe5b600160a060020a039092166020928302909101909101526001016128ff565b60038483600302815181101515612a8457fe5b60ff9092166020928302909101909101526005548351600160a060020a0390911690849060038502908110612ab557fe5b600160a060020a039092166020928302909101909101528351600490859060016003860201908110612ae357fe5b60ff9092166020928302909101909101526006548351600160a060020a0390911690849060038502600101908110612b1757fe5b600160a060020a039092166020928302909101909101525b509194909350915050565b600081565b606481565b600080838360328211801590612b5a5750818111155b8015612b6557508015155b8015612b7057508115155b1515612bb4576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b7388bb3838aa0a140acb73eeb3d4b25a8d3afd58d43314612c1f576040805160e560020a62461bcd02815260206004820152600f60248201527f496e76616c69642073656e6465722e0000000000000000000000000000000000604482015290519081900360640190fd5b600c54610100900460ff1615612c7f576040805160e560020a62461bcd02815260206004820152601460248201527f416c726561647920636f6e73747275637465642e000000000000000000000000604482015290519081900360640190fd5b600c805460ff19169055859350600092505b83831015612da65760026000888886818110612ca957fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff16158015612d015750868684818110612ce357fe5b90506020020135600160a060020a0316600160a060020a0316600014155b1515612d57576040805160e560020a62461bcd02815260206004820152601e60248201527f41646472657373206973206e756c6c206f72206e6f7420756e697175652e0000604482015290519081900360640190fd5b600160026000898987818110612d6957fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff191691151591909117905560019290920191612c91565b612db260008888615a46565b506001859055600c805461ff001916610100179055604080518082018252600b81527f41646472657373426f6f6b00000000000000000000000000000000000000000060208083019182528351938401899052606080855283519085015282517fc5caa942b8f8ea45a2e094d941dbba0ef9c0307f34c81ce78e71bfb128d6b25a946000938b939192839283019160808401918083838a5b83811015612e62578181015183820152602001612e4a565b50505050905090810190601f168015612e8f5780820380516001836020036101000a031916815260200191505b508381038252858181548152602001915080548015612ed757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311612eb9575b50509550505050505060405180910390a150505050505050565b600160a060020a038116600090815260026020526040812054829060ff161515612f53576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515612faa576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b83600160a060020a0381161515612ff9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b60005461300d90600163ffffffff61591416565b600154603282111580156130215750818111155b801561302c57508015155b801561303757508115155b151561307b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6130926002600160a060020a038916600080614af1565b95506130ab6002600160a060020a038916600080614bc4565b6130b4866151f5565b80156130df5750600160008781526003602052604090206006015460ff1660048111156130dd57fe5b145b156130ed576130ed86615210565b50505050505050565b60008181526003602081815260408084208054600182015460028301549583015460058401546006850154600490950180548751818a0281018a019098528088528a998a998a996060998b998a9960ff91821699909895979096909594919092169291859183018282801561319457602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311613176575b505050505092509650965096509650965096509650919395979092949650565b60008080808085600160a060020a0381161515613209576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600a546000965086955093508492505b8383101561326457600a80543391908590811061323257fe5b600091825260209091200154600160a060020a031614156132595760019550829450613264565b600190920191613219565b8515156132bb576040805160e560020a62461bcd02815260206004820152601e60248201527f4d73672e73656e646572206973206e6f7420434e20636f6e74726163742e0000604482015290519081900360640190fd5b600b8054869081106132c957fe5b600091825260209091200154600b8054600160a060020a0390921693508891879081106132f257fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055507faa5c92ffd739bc0b8b117b671e7d713917ddb1440b436263a3ea106d70c6f05f60098681548110151561335057fe5b600091825260209091200154600b8054600160a060020a03909216918591908990811061337957fe5b6000918252602091829020015460408051600160a060020a03958616815293851692840192909252929092168183015290519081900360600190a150505050505050565b3360008181526002602052604081205490919060ff161515613417576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff161561348a576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b83600160a060020a03811615156134d9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b6000546134ed90600163ffffffff6159e816565b600154603282111580156135015750818111155b801561350c57508015155b801561351757508115155b151561355b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6135726001600160a060020a038916600080614af1565b95506130ab6001600160a060020a038916600080614bc4565b3360008181526002602052604081205490919060ff1615156135e5576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6135f3600460008080614af1565b9150613603600460008080614bc4565b61360c826151f5565b80156136375750600160008381526003602052604090206006015460ff16600481111561363557fe5b145b156136455761364582615210565b5050565b62093a8081565b600082600160a060020a03811615156136a1576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff1615156136f8576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561373757600080fd5b505af115801561374b573d6000803e3d6000fd5b505050506040513d602081101561376157600080fd5b5051146137b8576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b6137cf6007600160a060020a038716866000614af1565b9250610fef6007600160a060020a038716866000614bc4565b6212750081565b333014613834576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b60078054600160a060020a031916600160a060020a03838116919091179182905560408051929091168252517f508aacd44cfe23a34a8c2643ab08c3410cf5505632cfce58dcfa0efa2fd2ff37916020908290030190a150565b600381565b60008082600160a060020a03811615156138e5576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff16151561393c576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a03851660008181526008602052604090205460098054919650908690811061396757fe5b600091825260209091200154600160a060020a0316146139bf576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b600954600110613a19576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b613a306009600160a060020a038716600080614af1565b9250610fef6009600160a060020a038716600080614bc4565b60015481565b600654600160a060020a031681565b333014613aa3576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b6000548160328211801590613ab85750818111155b8015613ac357508015155b8015613ace57508115155b1515613b12576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154831415613b6c576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b6001839055613b7961216f565b6040805184815290517f8951393946c27b45080aad111464c16c70f3d5e7d24b114a627334441961bf5f9181900360200190a1505050565b600080333014613bf9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613c3857600080fd5b505af1158015613c4c573d6000803e3d6000fd5b505050506040513d6020811015613c6257600080fd5b505114613cb9576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060058054600160a060020a03848116600160a060020a03198316179092551660008115613d4f5781600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613d2057600080fd5b505af1158015613d34573d6000803e3d6000fd5b505050506040513d6020811015613d4a57600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517fd531725ac89042f190fd73adfdeff435e07500f1a92b4b87743f1bcf91acb3a79181900360800190a150505050565b600083600160a060020a0381161515613df7576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e46576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e95576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515613eec576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60095460001015613f8a57600160a060020a038816600081815260086020526040902054600980549091908110613f1f57fe5b600091825260209091200154600160a060020a03161415613f8a576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b87600160a060020a031687600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613fd257600080fd5b505af1158015613fe6573d6000803e3d6000fd5b505050506040513d6020811015613ffc57600080fd5b5051600160a060020a03161461404a576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b85600160a060020a031687600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561409257600080fd5b505af11580156140a6573d6000803e3d6000fd5b505050506040513d60208110156140bc57600080fd5b5051600160a060020a03161461411c576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b86600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561415a57600080fd5b505af115801561416e573d6000803e3d6000fd5b505050506040513d602081101561418457600080fd5b505115156001146141df576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b6141fb6008600160a060020a03808b16908a8116908a16614af1565b94506142196008600160a060020a03808b16908a8116908a16614bc4565b614222856151f5565b801561424d5750600160008681526003602052604090206006015460ff16600481111561424b57fe5b145b1561425b5761425b85615210565b5050505050505050565b3330146142aa576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600c5460ff1615614305576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b600054151561435e576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a031615156143c0576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614422576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600954151561447b576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a54600954146144fc576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a541461457d576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b600c805460ff191660011790556040517f29d89931226d613bf878a0be8c635eaf2049121c8c68d5ad80a78f0ac9005d4b90600090a1565b600554600160a060020a031681565b6060600480548060200260200160405190810160405280929190818152602001828054801561461357602002820191906000526020600020905b815481526001909101906020018083116145fe575b505050505090505b90565b600181565b3360008181526002602052604081205490919060ff16151561467d576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60005483603282118015906146925750818111155b801561469d57508015155b80156146a857508115155b15156146ec576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154851415614746576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b614754600386600080614af1565b9350614764600386600080614bc4565b61476d846151f5565b80156147985750600160008581526003602052604090206006015460ff16600481111561479657fe5b145b156110315761103184615210565b3360008181526002602052604081205490919060ff161515614800576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600c5460ff161561485b576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b60005415156148b4576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a03161515614916576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614978576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b60095415156149d1576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a5460095414614a52576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a5414614ad3576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b614ae1600560008080614af1565b9150613603600560008080614bc4565b6000848484846040516020018085600a811115614b0a57fe5b60ff167f010000000000000000000000000000000000000000000000000000000000000002815260018101949094525060218301919091526041808301919091526040805180840390920182526061909201918290528051909250819060208401908083835b60208310614b8f5780518252601f199092019160209182019101614b70565b5181516020939093036101000a6000190180199091169216919091179052604051920182900390912098975050505050505050565b6000806000614bd587878787614af1565b60008181526003602052604090206005015490935015614f5e5760008381526003602052604090206005015442621275009091011015614c6a57614c188361592b565b60008381526003602081905260408220805460ff19168155600181018390556002810183905590810182905590614c526004830182615a25565b5060006005820155600601805460ff19169055614f5e565b6000838152600360205260409020600501544262093a809091011015614da257600460008481526003602052604090206006015460ff166004811115614cac57fe5b14614cce576000838152600360205260409020600601805460ff191660041790555b600083815260036020526040908190209051339185917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918b918b918b918b91600401908086600a811115614d1f57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614d8b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614d6d575b5050965050505050505060405180910390a3614f5e565b6000838152600360205260409020600501544210614f5e575050600081815260036020526040812060040154905b81811015614e69576000838152600360205260409020600401805482908110614df557fe5b600091825260209091200154600160a060020a0316331415614e61576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220616c7265616479207265717565737465642e000000604482015290519081900360640190fd5b600101614dd0565b600083815260036020908152604080832060040180546001810182558185529284209092018054600160a060020a03191633908117909155928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a811115614edf57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614f4b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614f2d575b5050965050505050505060405180910390a35b60008381526003602052604090206005015415156130ed57600454606411614fe357600487600a811115614f8e57fe5b14614fe3576040805160e560020a62461bcd02815260206004820152601560248201527f52657175657374206c6973742069732066756c6c2e0000000000000000000000604482015290519081900360640190fd5b60e06040519081016040528088600a811115614ffb57fe5b81526020808201899052604080830189905260608301889052805160008082529281019091526080909201919050815242602082015260400160019052600084815260036020526040902081518154829060ff1916600183600a81111561505e57fe5b021790555060208281015160018301556040830151600283015560608301516003830155608083015180516150999260048501920190615aa9565b5060a0820151600582015560c082015160068201805460ff191660018360048111156150c157fe5b0217905550505060008381526003602090815260408083206004908101805460018181018355828752948620018054600160a060020a0319163390811790915582549485019092557f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909301879055928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a81111561516e57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c00190849080156151da57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116151bc575b5050965050505050505060405180910390a350505050505050565b60015460009182526003602052604090912060040154101590565b600061521a615afe565b600083815260036020526040808220815160e0810190925280549294509091829060ff16600a81111561524957fe5b600a81111561525457fe5b8152600182015460208083019190915260028301546040808401919091526003840154606084015260048401805482518185028101850190935280835260809094019391929091908301828280156152d557602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116152b7575b505050918352505060058201546020820152600682015460409091019060ff16600481111561530057fe5b600481111561530b57fe5b905250905060018151600a81111561531f57fe5b1415615389576020810151604080517f70480275000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916370480275916024808301926000929190829003018183875af192505050915061579a565b60028151600a81111561539857fe5b1415615402576020810151604080517f27e1f7df000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916327e1f7df916024808301926000929190829003018183875af192505050915061579a565b60038151600a81111561541157fe5b1415615473576020810151604080517fc47afb3a000000000000000000000000000000000000000000000000000000008152600481019290925251309163c47afb3a916024808301926000929190829003018183875af192505050915061579a565b60048151600a81111561548257fe5b14156154c55730600160a060020a0316634f97638f6040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60058151600a8111156154d457fe5b14156155175730600160a060020a031663cec924666040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60068151600a81111561552657fe5b141561559957602081015160408083015181517fc7e9de75000000000000000000000000000000000000000000000000000000008152600160a060020a039093166004840152602483015251309163c7e9de75916044808301926000929190829003018183875af192505050915061579a565b60078151600a8111156155a857fe5b141561561b57602081015160408083015181517f4c5d435c000000000000000000000000000000000000000000000000000000008152600160a060020a0390931660048401526024830152513091634c5d435c916044808301926000929190829003018183875af192505050915061579a565b60088151600a81111561562a57fe5b14156156ad576020810151604080830151606084015182517f298b3c61000000000000000000000000000000000000000000000000000000008152600160a060020a0394851660048201529184166024830152909216604483015251309163298b3c61916064808301926000929190829003018183875af192505050915061579a565b60098151600a8111156156bc57fe5b1415615726576020810151604080517f579740db000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163579740db916024808301926000929190829003018183875af192505050915061579a565b600a8151600a81111561573557fe5b141561579a576020810151604080517fafaaf330000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163afaaf330916024808301926000929190829003018183875af194505050505b6157a38361592b565b811561585d57600083815260036020526040902060050154156157dd576000838152600360205260409020600601805460ff191660021790555b33600160a060020a031683600019167fc55c9229184beabeee72b6970a96691b4200919e47579cc4b9be50a1bec7ef1183600001518460200151856040015186606001516040518085600a81111561583157fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a36117f1565b60008381526003602052604090206005015415615894576000838152600360208190526040909120600601805460ff191690911790555b33600160a060020a031683600019167ff151a3ee41626c2511372320f09f7957af81c8c1cde8cdf3f05a5979626eaaf383600001518460200151856040015186606001516040518085600a8111156158e857fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a3505050565b6000808383111561592457600080fd5b5050900390565b60045460005b818110156117f157600480548290811061594757fe5b6000918252602090912001548314156159e0576000198201811461599e5760048054600019840190811061597757fe5b906000526020600020015460048281548110151561599157fe5b6000918252602090912001555b6004805460001984019081106159b057fe5b60009182526020822001556004546159cf90600163ffffffff61591416565b6159da600482615a01565b506117f1565b600101615931565b6000828201838110156159fa57600080fd5b9392505050565b8154818355818111156117f1576000838152602090206117f1918101908301615b3b565b5080546000825590600052602060002090810190615a439190615b3b565b50565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a99578154600160a060020a031916600160a060020a03843516178255602090920191600190910190615a66565b50615aa5929150615b55565b5090565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a995782518254600160a060020a031916600160a060020a03909116178255602090920191600190910190615ac9565b6040805160e081019091528060008152600060208201819052604082018190526060808301829052608083015260a0820181905260c09091015290565b61461b91905b80821115615aa55760008155600101615b41565b61461b91905b80821115615aa5578054600160a060020a0319168155600101615b5b560041646472657373206973206e6f742061646d696e2e0000000000000000000000496e76616c696420434e206e6f64652049442e0000000000000000000000000041646472657373206973206e756c6c2e000000000000000000000000000000004e6f742061206d756c74697369672d7472616e73616374696f6e2e0000000000496e76616c696420726571756972656d656e742e000000000000000000000000a165627a7a7230582007320485103bb418db53fd7eb24f69d0f455d28d1b5b146eb3e7fa6237cb69f10029","balance":"0x0"},"73718c4980728857f3aa5148e9d1b471efa3a7dd":{"balance":"0x446c3b15f9926687d2c40534fdb564000000000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}' > /klaytn/genesis.json + echo '{"config":{"chainId":2019,"istanbulCompatibleBlock":0,"londonCompatibleBlock":0,"ethTxTypeCompatibleBlock":0,"istanbul":{"epoch":604800,"policy":2,"sub":13},"unitPrice":25000000000,"deriveShaImpl":2,"governance":{"governingNode":"0x73718c4980728857f3aa5148e9d1b471efa3a7dd","governanceMode":"single","reward":{"mintingAmount":9600000000000000000,"ratio":"34/54/12","useGiniCoeff":false,"deferredTxFee":true,"stakingUpdateInterval":86400,"proposerUpdateInterval":3600,"minimumStake":5000000}}},"timestamp":"0x5cef3d41","extraData":"0x0000000000000000000000000000000000000000000000000000000000000000f85ad59473718c4980728857f3aa5148e9d1b471efa3a7ddb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","governanceData":null,"blockScore":"0x1","alloc":{"0000000000000000000000000000000000000400":{"code":"0x6080604052600436106102215763ffffffff60e060020a60003504166315575d5a8114610226578063160370b8146102725780631865c57d1461037c57806321ac4ad4146103ec57806327e1f7df14610412578063298b3c611461043357806332b91e8514610460578063394a144a1461048b5780633f0628b1146104ac578063407091eb146104d057806341b6945c1461056c5780634a8c1fb4146105815780634b6a94cc146105aa5780634c5d435c146106345780634f97638f1461065857806350a5bb691461066d578063579740db1461068257806358d65880146106a35780636abd623d146106ca57806370480275146106fb578063715b208b1461071c57806376674c54146107ca578063778f39cb146107df5780637894c366146107f4578063791b51231461081857806382d67e5a14610839578063832a2aad146108d5578063863f5c0a146108f657806387cd9feb146109175780638e6f6b771461092c5780639258d76814610941578063934d1fa414610965578063afaaf3301461097a578063b50060e41461099b578063b5067706146109b0578063b7563930146109d1578063b858dd95146109e6578063c47afb3a146109fb578063c7e9de7514610a13578063cc11efc014610a37578063cec9246614610a64578063d267eda514610a79578063da34a0bd14610a8e578063de5bbfbc14610af3578063e748357b14610b08578063feb15ca114610b20578063ffa1ad7414610b35575b600080fd5b34801561023257600080fd5b50610247600160a060020a0360043516610b4a565b60408051600160a060020a039485168152928416602084015292168183015290519081900360600190f35b34801561027e57600080fd5b50610287610ca0565b60408051600160a060020a0380851660608301528316608082015260a080825287519082015286519091829160208084019284019160c08501918b8101910280838360005b838110156102e45781810151838201526020016102cc565b50505050905001848103835288818151815260200191508051906020019060200280838360005b8381101561032357818101518382015260200161030b565b50505050905001848103825287818151815260200191508051906020019060200280838360005b8381101561036257818101518382015260200161034a565b505050509050019850505050505050505060405180910390f35b34801561038857600080fd5b50610391610deb565b6040518080602001838152602001828103825284818151815260200191508051906020019060200280838360005b838110156103d75781810151838201526020016103bf565b50505050905001935050505060405180910390f35b3480156103f857600080fd5b50610410600160a060020a0360043516602435610e57565b005b34801561041e57600080fd5b50610410600160a060020a0360043516611038565b34801561043f57600080fd5b50610410600160a060020a03600435811690602435811690604435166112c9565b34801561046c57600080fd5b50610475611720565b6040805160ff9092168252519081900360200190f35b34801561049757600080fd5b50610410600160a060020a0360043516611725565b3480156104b857600080fd5b5061041060ff600435166024356044356064356117f6565b3480156104dc57600080fd5b506104f460ff60043516602435604435606435611e88565b60408051858152908101839052602081016060820183600481111561051557fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360005b8381101561055557818101518382015260200161053d565b505050509050019550505050505060405180910390f35b34801561057857600080fd5b50610475611f35565b34801561058d57600080fd5b50610596611f3a565b604080519115158252519081900360200190f35b3480156105b657600080fd5b506105bf611f43565b6040805160208082528351818301528351919283929083019185019080838360005b838110156105f95781810151838201526020016105e1565b50505050905090810190601f1680156106265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561064057600080fd5b50610410600160a060020a0360043516602435611f7a565b34801561066457600080fd5b5061041061216f565b34801561067957600080fd5b50610596612274565b34801561068e57600080fd5b50610410600160a060020a0360043516612282565b3480156106af57600080fd5b506106b8612658565b60408051918252519081900360200190f35b3480156106d657600080fd5b506106df61265d565b60408051600160a060020a039092168252519081900360200190f35b34801561070757600080fd5b50610410600160a060020a036004351661266c565b34801561072857600080fd5b5061073161284b565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561077557818101518382015260200161075d565b50505050905001838103825284818151815260200191508051906020019060200280838360005b838110156107b457818101518382015260200161079c565b5050505090500194505050505060405180910390f35b3480156107d657600080fd5b50610475612b3a565b3480156107eb57600080fd5b506106b8612b3f565b34801561080057600080fd5b50610410602460048035828101929101359035612b44565b34801561082457600080fd5b50610410600160a060020a0360043516612ef1565b34801561084557600080fd5b506108516004356130f6565b6040518088600a81111561086157fe5b60ff16815260208101889052604081018790526060810186905260a08101849052608081019060c00183600481111561089657fe5b60ff168152602001828103825285818151815260200191508051906020019060200280838360008381101561036257818101518382015260200161034a565b3480156108e157600080fd5b50610410600160a060020a03600435166131b4565b34801561090257600080fd5b50610410600160a060020a03600435166133bd565b34801561092357600080fd5b5061041061358b565b34801561093857600080fd5b506106b8613649565b34801561094d57600080fd5b50610410600160a060020a0360043516602435613650565b34801561097157600080fd5b506106b86137e8565b34801561098657600080fd5b50610410600160a060020a03600435166137ef565b3480156109a757600080fd5b5061047561388e565b3480156109bc57600080fd5b50610410600160a060020a0360043516613893565b3480156109dd57600080fd5b506106b8613a49565b3480156109f257600080fd5b506106df613a4f565b348015610a0757600080fd5b50610410600435613a5e565b348015610a1f57600080fd5b50610410600160a060020a0360043516602435613bb1565b348015610a4357600080fd5b50610410600160a060020a0360043581169060243581169060443516613da6565b348015610a7057600080fd5b50610410614265565b348015610a8557600080fd5b506106df6145b5565b348015610a9a57600080fd5b50610aa36145c4565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610adf578181015183820152602001610ac7565b505050509050019250505060405180910390f35b348015610aff57600080fd5b5061047561461e565b348015610b1457600080fd5b50610410600435614623565b348015610b2c57600080fd5b506104106147a6565b348015610b4157600080fd5b506106b861461e565b600080808084600160a060020a0381161515610b9e576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600160a060020a038616600081815260086020526040902054600980549194509084908110610bc957fe5b600091825260209091200154600160a060020a031614610c21576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009805483908110610c2f57fe5b600091825260209091200154600a8054600160a060020a039092169184908110610c5557fe5b600091825260209091200154600b8054600160a060020a039092169185908110610c7b57fe5b6000918252602090912001549196509450600160a060020a0316925050509193909250565b60055460065460098054604080516020808402820181019092528281526060958695869560009586959194600a94600b94600160a060020a03918216949116928791830182828015610d1b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610cfd575b5050505050945083805480602002602001604051908101604052809291908181526020018280548015610d7757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610d59575b5050505050935082805480602002602001604051908101604052809291908181526020018280548015610dd357602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610db5575b50505050509250945094509450945094509091929394565b606060008060015481805480602002602001604051908101604052809291908181526020018280548015610e4857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610e2a575b50505050509150915091509091565b600082600160a060020a0381161515610ea8576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515610eff576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015610f3e57600080fd5b505af1158015610f52573d6000803e3d6000fd5b505050506040513d6020811015610f6857600080fd5b505114610fbf576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b610fd66006600160a060020a038716866000614af1565b9250610fef6006600160a060020a038716866000614bc4565b610ff8836151f5565b80156110235750600160008481526003602052604090206006015460ff16600481111561102157fe5b145b156110315761103183615210565b5050505050565b600080333014611080576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff1615156110e2576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6000546110f690600163ffffffff61591416565b6001546032821115801561110a5750818111155b801561111557508015155b801561112057508115155b1515611164576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b60008054600160a060020a0388168252600260205260408220805460ff19169055955093505b6001850384101561123d5785600160a060020a03166000858154811015156111ae57fe5b600091825260209091200154600160a060020a03161415611232576000805460001987019081106111db57fe5b60009182526020822001548154600160a060020a039091169190869081106111ff57fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a0316021790555061123d565b60019093019261118a565b60008054600019870190811061124f57fe5b600091825260208220018054600160a060020a03191690555461127990600163ffffffff61591416565b611284600082615a01565b5061128d61216f565b604051600160a060020a038716907f1af6bd3d85a56e7c4a0700756fd2ca3b3b65c266e56c77652c5a346bc256522090600090a2505050505050565b6000333014611310576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600954600010156113ae57600160a060020a03841660008181526008602052604090205460098054909190811061134357fe5b600091825260209091200154600160a060020a031614156113ae576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b83600160a060020a031683600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156113f657600080fd5b505af115801561140a573d6000803e3d6000fd5b505050506040513d602081101561142057600080fd5b5051600160a060020a03161461146e576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b81600160a060020a031683600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156114b657600080fd5b505af11580156114ca573d6000803e3d6000fd5b505050506040513d60208110156114e057600080fd5b5051600160a060020a031614611540576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b82600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561157e57600080fd5b505af1158015611592573d6000803e3d6000fd5b505050506040513d60208110156115a857600080fd5b50511515600114611603576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b5060098054600160a060020a03808616600081815260086020908152604080832086905560018087019097557f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af86018054600160a060020a03199081168617909155600a8054808a019091557fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80180548b88169083168117909155600b8054998a0181559094527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db99097018054958916959097168517909655855192835282015280840191909152915190917fe01726557c1ea9f7286dca4bba890e96fea9041689db298806306cafa74c9e91919081900360600190a150505050565b600281565b3360008181526002602052604081205490919060ff16151561177f576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b611796600a600160a060020a038516600080614af1565b91506117af600a600160a060020a038516600080614bc4565b6117b8826151f5565b80156117e35750600160008381526003602052604090206006015460ff1660048111156117e157fe5b145b156117f1576117f182615210565b505050565b33600081815260026020526040812054909182918291829160ff161515611855576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b61186189898989614af1565b60008181526003602052604090206005015490955015156118cc576040805160e560020a62461bcd02815260206004820152601060248201527f496e76616c696420726571756573742e00000000000000000000000000000000604482015290519081900360640190fd5b600160008681526003602052604090206006015460ff1660048111156118ee57fe5b14611943576040805160e560020a62461bcd02815260206004820152601f60248201527f4d757374206265206174206e6f742d636f6e6669726d65642073746174652e00604482015290519081900360640190fd5b60008581526003602052604081206004015490945092508391505b82821015611e2657600085815260036020526040902060040180548390811061198357fe5b600091825260209091200154600160a060020a0316331415611e1b57600085815260036020526040902060050154600194504262093a809091011015611b2f5760008581526003602052604090206005015442621275009091011015611a3e576119ec8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611a266004830182615a25565b5060006005820155600601805460ff19169055611a5b565b6000858152600360205260409020600601805460ff191660041790555b600085815260036020526040908190209051339187917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918d918d918d918d91600401908086600a811115611aac57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611b1857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611afa575b5050965050505050505060405180910390a3611e16565b60001983018214611bc057600085815260036020526040902060040180546000198501908110611b5b57fe5b6000918252602080832090910154878352600390915260409091206004018054600160a060020a039092169184908110611b9157fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055505b600085815260036020526040902060040180546000198501908110611be157fe5b600091825260208083209091018054600160a060020a0319169055868252600390526040902060040154611c1c90600163ffffffff61591416565b6000868152600360205260409020611c379060040182615a01565b5060008581526003602081905260409182902080546001820154600283015493830154945133958b957f9c174b2536ba49e3478ca649dac74d9e9f71f70adf70f193e780eabbfcdc367c9560ff9095169490926004909101908086600a811115611c9d57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015611d0957602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611ceb575b5050965050505050505060405180910390a36000858152600360205260409020600401541515611e1657611d3c8561592b565b60008581526003602081905260408220805460ff19168155600181018390556002810183905590810182905590611d766004830182615a25565b506000600582018190556006909101805460ff19169055858152600360208190526040918290208054600182015460028301549290930154935133948a947fbfda049a0206fd9c90ed4a3170f5bfaad83c323a16835dd68fea92faa247c2cd9460ff9094169390929091908085600a811115611dee57fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a35b611e26565b60019091019061195e565b831515611e7d576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220686173206e6f74207265717565737465642e000000604482015290519081900360640190fd5b505050505050505050565b600060606000806000611e9d89898989614af1565b600081815260036020908152604091829020600581015460068201546004909201805485518186028101860190965280865295965086959094919360ff90931692859190830182828015611f1a57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311611efc575b50505050509250945094509450945050945094509450949050565b600481565b600c5460ff1681565b60408051808201909152600b81527f41646472657373426f6f6b000000000000000000000000000000000000000000602082015281565b600080333014611fc2576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561200157600080fd5b505af1158015612015573d6000803e3d6000fd5b505050506040513d602081101561202b57600080fd5b505114612082576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060068054600160a060020a03848116600160a060020a031983161790925516600081156121185781600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b1580156120e957600080fd5b505af11580156120fd573d6000803e3d6000fd5b505050506040513d602081101561211357600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517ffdccdf242038c2d09605009fbb95e03f75cdbd106d0a9e52a1670be9553c88489181900360800190a150505050565b6000803330146121b7576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b505060045460005b8181101561223b57600360006004838154811015156121da57fe5b600091825260208083209091015483528201929092526040018120805460ff19168155600181018290556002810182905560038101829055906122206004830182615a25565b5060006005820155600601805460ff191690556001016121bf565b61224760046000615a25565b6040517f907527d30089abd16e30f06ddbbbc18480505176262f19bc16c1fbf9262f9c6b90600090a15050565b600c54610100900460ff1681565b60003330146122c9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b50600160a060020a03811660008181526008602052604090205460098054919291839081106122f457fe5b600091825260209091200154600160a060020a03161461234c576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b6009546001106123a6576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b6009546000190181101561251e576009805460001981019081106123c657fe5b60009182526020909120015460098054600160a060020a0390921691839081106123ec57fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600a805490916000190190811061242b57fe5b600091825260209091200154600a8054600160a060020a03909216918390811061245157fe5b60009182526020909120018054600160a060020a031916600160a060020a0392909216919091179055600954600b805490916000190190811061249057fe5b600091825260209091200154600b8054600160a060020a0390921691839081106124b657fe5b600091825260208220018054600160a060020a031916600160a060020a039390931692909217909155600980548392600892909160001981019081106124f857fe5b6000918252602080832090910154600160a060020a031683528201929092526040019020555b600160a060020a03821660009081526008602052604081205560098054600019810190811061254957fe5b60009182526020909120018054600160a060020a031916905560095461257690600163ffffffff61591416565b612581600982615a01565b50600a8054600019810190811061259457fe5b60009182526020909120018054600160a060020a0319169055600a546125c190600163ffffffff61591416565b6125cc600a82615a01565b50600b805460001981019081106125df57fe5b60009182526020909120018054600160a060020a0319169055600b5461260c90600163ffffffff61591416565b612617600b82615a01565b5060408051600160a060020a038416815290517fa30079721e55931e89e7cdb421712ad0fcc817e7cac8fe954aa7ed0d46b9c42d9181900360200190a15050565b603281565b600754600160a060020a031681565b3330146126b1576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600160a060020a038116600090815260026020526040902054819060ff1615612724576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b60005461273890600163ffffffff6159e816565b6001546032821115801561274c5750818111155b801561275757508015155b801561276257508115155b15156127a6576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600160a060020a0384166000818152600260205260408120805460ff19166001908117909155815490810182559080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563018054600160a060020a031916909117905561281161216f565b604051600160a060020a038516907fad6de4452a631e641cb59902236607946ce9272b9b981f2f80e8d129cb9084ba90600090a250505050565b600c54606090819081908190600090819060ff161515612884576040805160008082526020820190815281830190925294509250612b2f565b6009805490506003026002016040519080825280602002602001820160405280156128b9578160200160208202803883390190505b5093506009805490506003026002016040519080825280602002602001820160405280156128f1578160200160208202803883390190505b506009549093509150600090505b81811015612a71576000848260030281518110151561291a57fe5b60ff909216602092830290910190910152600980548290811061293957fe5b6000918252602090912001548351600160a060020a039091169084906003840290811061296257fe5b600160a060020a039092166020928302909101909101528351600190859060038402830190811061298f57fe5b60ff909216602092830290910190910152600a8054829081106129ae57fe5b6000918252602090912001548351600160a060020a03909116908490600384026001019081106129da57fe5b600160a060020a0390921660209283029091019091015283516002908590600384028301908110612a0757fe5b60ff909216602092830290910190910152600b805482908110612a2657fe5b6000918252602090912001548351600160a060020a0390911690849060026003850201908110612a5257fe5b600160a060020a039092166020928302909101909101526001016128ff565b60038483600302815181101515612a8457fe5b60ff9092166020928302909101909101526005548351600160a060020a0390911690849060038502908110612ab557fe5b600160a060020a039092166020928302909101909101528351600490859060016003860201908110612ae357fe5b60ff9092166020928302909101909101526006548351600160a060020a0390911690849060038502600101908110612b1757fe5b600160a060020a039092166020928302909101909101525b509194909350915050565b600081565b606481565b600080838360328211801590612b5a5750818111155b8015612b6557508015155b8015612b7057508115155b1515612bb4576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b7388bb3838aa0a140acb73eeb3d4b25a8d3afd58d43314612c1f576040805160e560020a62461bcd02815260206004820152600f60248201527f496e76616c69642073656e6465722e0000000000000000000000000000000000604482015290519081900360640190fd5b600c54610100900460ff1615612c7f576040805160e560020a62461bcd02815260206004820152601460248201527f416c726561647920636f6e73747275637465642e000000000000000000000000604482015290519081900360640190fd5b600c805460ff19169055859350600092505b83831015612da65760026000888886818110612ca957fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff16158015612d015750868684818110612ce357fe5b90506020020135600160a060020a0316600160a060020a0316600014155b1515612d57576040805160e560020a62461bcd02815260206004820152601e60248201527f41646472657373206973206e756c6c206f72206e6f7420756e697175652e0000604482015290519081900360640190fd5b600160026000898987818110612d6957fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff191691151591909117905560019290920191612c91565b612db260008888615a46565b506001859055600c805461ff001916610100179055604080518082018252600b81527f41646472657373426f6f6b00000000000000000000000000000000000000000060208083019182528351938401899052606080855283519085015282517fc5caa942b8f8ea45a2e094d941dbba0ef9c0307f34c81ce78e71bfb128d6b25a946000938b939192839283019160808401918083838a5b83811015612e62578181015183820152602001612e4a565b50505050905090810190601f168015612e8f5780820380516001836020036101000a031916815260200191505b508381038252858181548152602001915080548015612ed757602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311612eb9575b50509550505050505060405180910390a150505050505050565b600160a060020a038116600090815260026020526040812054829060ff161515612f53576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515612faa576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b83600160a060020a0381161515612ff9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b60005461300d90600163ffffffff61591416565b600154603282111580156130215750818111155b801561302c57508015155b801561303757508115155b151561307b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6130926002600160a060020a038916600080614af1565b95506130ab6002600160a060020a038916600080614bc4565b6130b4866151f5565b80156130df5750600160008781526003602052604090206006015460ff1660048111156130dd57fe5b145b156130ed576130ed86615210565b50505050505050565b60008181526003602081815260408084208054600182015460028301549583015460058401546006850154600490950180548751818a0281018a019098528088528a998a998a996060998b998a9960ff91821699909895979096909594919092169291859183018282801561319457602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311613176575b505050505092509650965096509650965096509650919395979092949650565b60008080808085600160a060020a0381161515613209576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b600a546000965086955093508492505b8383101561326457600a80543391908590811061323257fe5b600091825260209091200154600160a060020a031614156132595760019550829450613264565b600190920191613219565b8515156132bb576040805160e560020a62461bcd02815260206004820152601e60248201527f4d73672e73656e646572206973206e6f7420434e20636f6e74726163742e0000604482015290519081900360640190fd5b600b8054869081106132c957fe5b600091825260209091200154600b8054600160a060020a0390921693508891879081106132f257fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055507faa5c92ffd739bc0b8b117b671e7d713917ddb1440b436263a3ea106d70c6f05f60098681548110151561335057fe5b600091825260209091200154600b8054600160a060020a03909216918591908990811061337957fe5b6000918252602091829020015460408051600160a060020a03958616815293851692840192909252929092168183015290519081900360600190a150505050505050565b3360008181526002602052604081205490919060ff161515613417576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a038316600090815260026020526040902054839060ff161561348a576040805160e560020a62461bcd02815260206004820152601460248201527f41646d696e20616c72656164792065786974732e000000000000000000000000604482015290519081900360640190fd5b83600160a060020a03811615156134d9576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b6000546134ed90600163ffffffff6159e816565b600154603282111580156135015750818111155b801561350c57508015155b801561351757508115155b151561355b576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b6135726001600160a060020a038916600080614af1565b95506130ab6001600160a060020a038916600080614bc4565b3360008181526002602052604081205490919060ff1615156135e5576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b6135f3600460008080614af1565b9150613603600460008080614bc4565b61360c826151f5565b80156136375750600160008381526003602052604090206006015460ff16600481111561363557fe5b145b156136455761364582615210565b5050565b62093a8081565b600082600160a060020a03811615156136a1576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff1615156136f8576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b8385600160a060020a031663444263466040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561373757600080fd5b505af115801561374b573d6000803e3d6000fd5b505050506040513d602081101561376157600080fd5b5051146137b8576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c6964204b49522076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b6137cf6007600160a060020a038716866000614af1565b9250610fef6007600160a060020a038716866000614bc4565b6212750081565b333014613834576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b60078054600160a060020a031916600160a060020a03838116919091179182905560408051929091168252517f508aacd44cfe23a34a8c2643ab08c3410cf5505632cfce58dcfa0efa2fd2ff37916020908290030190a150565b600381565b60008082600160a060020a03811615156138e5576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff16151561393c576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600160a060020a03851660008181526008602052604090205460098054919650908690811061396757fe5b600091825260209091200154600160a060020a0316146139bf576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b600954600110613a19576040805160e560020a62461bcd02815260206004820152601b60248201527f434e2073686f756c64206265206d6f7265207468616e206f6e652e0000000000604482015290519081900360640190fd5b613a306009600160a060020a038716600080614af1565b9250610fef6009600160a060020a038716600080614bc4565b60015481565b600654600160a060020a031681565b333014613aa3576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b6000548160328211801590613ab85750818111155b8015613ac357508015155b8015613ace57508115155b1515613b12576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154831415613b6c576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b6001839055613b7961216f565b6040805184815290517f8951393946c27b45080aad111464c16c70f3d5e7d24b114a627334441961bf5f9181900360200190a1505050565b600080333014613bf9576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b8284600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613c3857600080fd5b505af1158015613c4c573d6000803e3d6000fd5b505050506040513d6020811015613c6257600080fd5b505114613cb9576040805160e560020a62461bcd02815260206004820152601460248201527f496e76616c696420506f432076657273696f6e2e000000000000000000000000604482015290519081900360640190fd5b505060058054600160a060020a03848116600160a060020a03198316179092551660008115613d4f5781600160a060020a0316630f6100726040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613d2057600080fd5b505af1158015613d34573d6000803e3d6000fd5b505050506040513d6020811015613d4a57600080fd5b505190505b60408051600160a060020a038085168252602082018490528616818301526060810185905290517fd531725ac89042f190fd73adfdeff435e07500f1a92b4b87743f1bcf91acb3a79181900360800190a150505050565b600083600160a060020a0381161515613df7576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e46576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b83600160a060020a0381161515613e95576040805160e560020a62461bcd0281526020600482015260106024820152600080516020615bba833981519152604482015290519081900360640190fd5b3360008181526002602052604090205460ff161515613eec576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60095460001015613f8a57600160a060020a038816600081815260086020526040902054600980549091908110613f1f57fe5b600091825260209091200154600160a060020a03161415613f8a576040805160e560020a62461bcd02815260206004820152601960248201527f434e206e6f646520494420616c72656164792065786973742e00000000000000604482015290519081900360640190fd5b87600160a060020a031687600160a060020a031663139d7fed6040518163ffffffff1660e060020a028152600401602060405180830381600087803b158015613fd257600080fd5b505af1158015613fe6573d6000803e3d6000fd5b505050506040513d6020811015613ffc57600080fd5b5051600160a060020a03161461404a576040805160e560020a62461bcd0281526020600482015260136024820152600080516020615b9a833981519152604482015290519081900360640190fd5b85600160a060020a031687600160a060020a0316638cf57cb96040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561409257600080fd5b505af11580156140a6573d6000803e3d6000fd5b505050506040513d60208110156140bc57600080fd5b5051600160a060020a03161461411c576040805160e560020a62461bcd02815260206004820152601a60248201527f496e76616c696420434e2072657761726420616464726573732e000000000000604482015290519081900360640190fd5b86600160a060020a031663392e53cd6040518163ffffffff1660e060020a028152600401602060405180830381600087803b15801561415a57600080fd5b505af115801561416e573d6000803e3d6000fd5b505050506040513d602081101561418457600080fd5b505115156001146141df576040805160e560020a62461bcd02815260206004820152601f60248201527f434e20636f6e7472616374206973206e6f7420696e697469616c697a65642e00604482015290519081900360640190fd5b6141fb6008600160a060020a03808b16908a8116908a16614af1565b94506142196008600160a060020a03808b16908a8116908a16614bc4565b614222856151f5565b801561424d5750600160008681526003602052604090206006015460ff16600481111561424b57fe5b145b1561425b5761425b85615210565b5050505050505050565b3330146142aa576040805160e560020a62461bcd02815260206004820152601b6024820152600080516020615bda833981519152604482015290519081900360640190fd5b600c5460ff1615614305576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b600054151561435e576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a031615156143c0576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614422576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600954151561447b576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a54600954146144fc576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a541461457d576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b600c805460ff191660011790556040517f29d89931226d613bf878a0be8c635eaf2049121c8c68d5ad80a78f0ac9005d4b90600090a1565b600554600160a060020a031681565b6060600480548060200260200160405190810160405280929190818152602001828054801561461357602002820191906000526020600020905b815481526001909101906020018083116145fe575b505050505090505b90565b600181565b3360008181526002602052604081205490919060ff16151561467d576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b60005483603282118015906146925750818111155b801561469d57508015155b80156146a857508115155b15156146ec576040805160e560020a62461bcd0281526020600482015260146024820152600080516020615bfa833981519152604482015290519081900360640190fd5b600154851415614746576040805160e560020a62461bcd02815260206004820152601160248201527f53616d6520726571756972656d656e742e000000000000000000000000000000604482015290519081900360640190fd5b614754600386600080614af1565b9350614764600386600080614bc4565b61476d846151f5565b80156147985750600160008581526003602052604090206006015460ff16600481111561479657fe5b145b156110315761103184615210565b3360008181526002602052604081205490919060ff161515614800576040805160e560020a62461bcd0281526020600482015260156024820152600080516020615b7a833981519152604482015290519081900360640190fd5b600c5460ff161561485b576040805160e560020a62461bcd02815260206004820152601260248201527f416c7265616479206163746976617465642e0000000000000000000000000000604482015290519081900360640190fd5b60005415156148b4576040805160e560020a62461bcd02815260206004820152601360248201527f4e6f2061646d696e206973206c69737465642e00000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a03161515614916576040805160e560020a62461bcd02815260206004820152601f60248201527f506f4320636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b600654600160a060020a03161515614978576040805160e560020a62461bcd02815260206004820152601f60248201527f4b495220636f6e7472616374206973206e6f7420726567697374657265642e00604482015290519081900360640190fd5b60095415156149d1576040805160e560020a62461bcd02815260206004820152601560248201527f4e6f206e6f6465204944206973206c69737465642e0000000000000000000000604482015290519081900360640190fd5b600a5460095414614a52576040805160e560020a62461bcd02815260206004820152603660248201527f496e76616c6964206c656e677468206265747765656e206e6f6465204944732060448201527f616e64207374616b696e6720636f6e7472616374732e00000000000000000000606482015290519081900360840190fd5b600b54600a5414614ad3576040805160e560020a62461bcd02815260206004820152603e60248201527f496e76616c6964206c656e677468206265747765656e207374616b696e67206360448201527f6f6e74726163747320616e6420726577617264206164647265737365732e0000606482015290519081900360840190fd5b614ae1600560008080614af1565b9150613603600560008080614bc4565b6000848484846040516020018085600a811115614b0a57fe5b60ff167f010000000000000000000000000000000000000000000000000000000000000002815260018101949094525060218301919091526041808301919091526040805180840390920182526061909201918290528051909250819060208401908083835b60208310614b8f5780518252601f199092019160209182019101614b70565b5181516020939093036101000a6000190180199091169216919091179052604051920182900390912098975050505050505050565b6000806000614bd587878787614af1565b60008181526003602052604090206005015490935015614f5e5760008381526003602052604090206005015442621275009091011015614c6a57614c188361592b565b60008381526003602081905260408220805460ff19168155600181018390556002810183905590810182905590614c526004830182615a25565b5060006005820155600601805460ff19169055614f5e565b6000838152600360205260409020600501544262093a809091011015614da257600460008481526003602052604090206006015460ff166004811115614cac57fe5b14614cce576000838152600360205260409020600601805460ff191660041790555b600083815260036020526040908190209051339185917f9f3ca7a04988021200a04e0775f46648683bffe7203608269a66c371befe5685918b918b918b918b91600401908086600a811115614d1f57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614d8b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614d6d575b5050965050505050505060405180910390a3614f5e565b6000838152600360205260409020600501544210614f5e575050600081815260036020526040812060040154905b81811015614e69576000838152600360205260409020600401805482908110614df557fe5b600091825260209091200154600160a060020a0316331415614e61576040805160e560020a62461bcd02815260206004820152601d60248201527f4d73672e73656e64657220616c7265616479207265717565737465642e000000604482015290519081900360640190fd5b600101614dd0565b600083815260036020908152604080832060040180546001810182558185529284209092018054600160a060020a03191633908117909155928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a811115614edf57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c0019084908015614f4b57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311614f2d575b5050965050505050505060405180910390a35b60008381526003602052604090206005015415156130ed57600454606411614fe357600487600a811115614f8e57fe5b14614fe3576040805160e560020a62461bcd02815260206004820152601560248201527f52657175657374206c6973742069732066756c6c2e0000000000000000000000604482015290519081900360640190fd5b60e06040519081016040528088600a811115614ffb57fe5b81526020808201899052604080830189905260608301889052805160008082529281019091526080909201919050815242602082015260400160019052600084815260036020526040902081518154829060ff1916600183600a81111561505e57fe5b021790555060208281015160018301556040830151600283015560608301516003830155608083015180516150999260048501920190615aa9565b5060a0820151600582015560c082015160068201805460ff191660018360048111156150c157fe5b0217905550505060008381526003602090815260408083206004908101805460018181018355828752948620018054600160a060020a0319163390811790915582549485019092557f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909301879055928690525185917fb7b03afe355fcf2b1d00e020db2b1a902b9ee1b1c1d995626c1e18c957340ea8918b918b918b918b918086600a81111561516e57fe5b60ff16815260208101869052604081018590526060810184905260a082820381016080830190815284549183018290529160c00190849080156151da57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116151bc575b5050965050505050505060405180910390a350505050505050565b60015460009182526003602052604090912060040154101590565b600061521a615afe565b600083815260036020526040808220815160e0810190925280549294509091829060ff16600a81111561524957fe5b600a81111561525457fe5b8152600182015460208083019190915260028301546040808401919091526003840154606084015260048401805482518185028101850190935280835260809094019391929091908301828280156152d557602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116152b7575b505050918352505060058201546020820152600682015460409091019060ff16600481111561530057fe5b600481111561530b57fe5b905250905060018151600a81111561531f57fe5b1415615389576020810151604080517f70480275000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916370480275916024808301926000929190829003018183875af192505050915061579a565b60028151600a81111561539857fe5b1415615402576020810151604080517f27e1f7df000000000000000000000000000000000000000000000000000000008152600160a060020a0390921660048301525130916327e1f7df916024808301926000929190829003018183875af192505050915061579a565b60038151600a81111561541157fe5b1415615473576020810151604080517fc47afb3a000000000000000000000000000000000000000000000000000000008152600481019290925251309163c47afb3a916024808301926000929190829003018183875af192505050915061579a565b60048151600a81111561548257fe5b14156154c55730600160a060020a0316634f97638f6040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60058151600a8111156154d457fe5b14156155175730600160a060020a031663cec924666040518163ffffffff1660e060020a0281526004016000604051808303816000875af192505050915061579a565b60068151600a81111561552657fe5b141561559957602081015160408083015181517fc7e9de75000000000000000000000000000000000000000000000000000000008152600160a060020a039093166004840152602483015251309163c7e9de75916044808301926000929190829003018183875af192505050915061579a565b60078151600a8111156155a857fe5b141561561b57602081015160408083015181517f4c5d435c000000000000000000000000000000000000000000000000000000008152600160a060020a0390931660048401526024830152513091634c5d435c916044808301926000929190829003018183875af192505050915061579a565b60088151600a81111561562a57fe5b14156156ad576020810151604080830151606084015182517f298b3c61000000000000000000000000000000000000000000000000000000008152600160a060020a0394851660048201529184166024830152909216604483015251309163298b3c61916064808301926000929190829003018183875af192505050915061579a565b60098151600a8111156156bc57fe5b1415615726576020810151604080517f579740db000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163579740db916024808301926000929190829003018183875af192505050915061579a565b600a8151600a81111561573557fe5b141561579a576020810151604080517fafaaf330000000000000000000000000000000000000000000000000000000008152600160a060020a03909216600483015251309163afaaf330916024808301926000929190829003018183875af194505050505b6157a38361592b565b811561585d57600083815260036020526040902060050154156157dd576000838152600360205260409020600601805460ff191660021790555b33600160a060020a031683600019167fc55c9229184beabeee72b6970a96691b4200919e47579cc4b9be50a1bec7ef1183600001518460200151856040015186606001516040518085600a81111561583157fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a36117f1565b60008381526003602052604090206005015415615894576000838152600360208190526040909120600601805460ff191690911790555b33600160a060020a031683600019167ff151a3ee41626c2511372320f09f7957af81c8c1cde8cdf3f05a5979626eaaf383600001518460200151856040015186606001516040518085600a8111156158e857fe5b60ff168152602081019490945250604080840192909252606083015251908190036080019150a3505050565b6000808383111561592457600080fd5b5050900390565b60045460005b818110156117f157600480548290811061594757fe5b6000918252602090912001548314156159e0576000198201811461599e5760048054600019840190811061597757fe5b906000526020600020015460048281548110151561599157fe5b6000918252602090912001555b6004805460001984019081106159b057fe5b60009182526020822001556004546159cf90600163ffffffff61591416565b6159da600482615a01565b506117f1565b600101615931565b6000828201838110156159fa57600080fd5b9392505050565b8154818355818111156117f1576000838152602090206117f1918101908301615b3b565b5080546000825590600052602060002090810190615a439190615b3b565b50565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a99578154600160a060020a031916600160a060020a03843516178255602090920191600190910190615a66565b50615aa5929150615b55565b5090565b828054828255906000526020600020908101928215615a99579160200282015b82811115615a995782518254600160a060020a031916600160a060020a03909116178255602090920191600190910190615ac9565b6040805160e081019091528060008152600060208201819052604082018190526060808301829052608083015260a0820181905260c09091015290565b61461b91905b80821115615aa55760008155600101615b41565b61461b91905b80821115615aa5578054600160a060020a0319168155600101615b5b560041646472657373206973206e6f742061646d696e2e0000000000000000000000496e76616c696420434e206e6f64652049442e0000000000000000000000000041646472657373206973206e756c6c2e000000000000000000000000000000004e6f742061206d756c74697369672d7472616e73616374696f6e2e0000000000496e76616c696420726571756972656d656e742e000000000000000000000000a165627a7a7230582007320485103bb418db53fd7eb24f69d0f455d28d1b5b146eb3e7fa6237cb69f10029","balance":"0x0"},"73718c4980728857f3aa5148e9d1b471efa3a7dd":{"balance":"0x446c3b15f9926687d2c40534fdb564000000000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}' > /klaytn/genesis.json echo '["kni://b299bf5bb0b5e91ddf35e055bef9b69c38112bae582b589d4ab4955f6b0f8e790a182530bd57cff00cad1eb49c39d6ab66eaaa979c5d644681ee124649c5f00f@172.16.239.10:32323?discport=0\u0026ntype=cn"]' > /klaytn/static-nodes.json kcn --datadir "/klaytn" init "/klaytn/genesis.json" kcn \ diff --git a/build.gradle b/build.gradle index ab83e8525..c9f0db700 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ buildscript { ext.androidxJunitVersion = '1.1.2' ext.androidxTestRunnerVersion = '1.3.0' ext.androidxTestRulesVersion = '1.3.0' + ext.jacksonDataBindVersion = '2.13.1' } plugins { @@ -33,7 +34,7 @@ apply plugin: 'io.codearte.nexus-staging' allprojects { - version '1.6.4' + version '1.8.0' group 'com.klaytn.caver' description 'caver-java project' diff --git a/core/build.gradle b/core/build.gradle index 8a23099f8..e42ab9a3f 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -8,5 +8,6 @@ dependencies { } compile "com.github.ipfs:java-ipfs-http-client:$ipfsVersion" + implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDataBindVersion" } diff --git a/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java new file mode 100644 index 000000000..feba51d98 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/methods/response/AccessListResult.java @@ -0,0 +1,100 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.methods.response; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.web3j.protocol.core.Response; + +import java.io.IOException; + +public class AccessListResult extends Response { + + @Override + public void setResult(AccessListResultData result) { + super.setResult(result); + } + + @JsonDeserialize(using = AccessListResultData.AccessListResultDeserializer.class) + public static class AccessListResultData { + AccessList accessList; + String error; + String gasUsed; + + public AccessListResultData(AccessList accessList, String error, String gasUsed) { + this.accessList = accessList; + this.error = error; + this.gasUsed = gasUsed; + } + + public AccessList getAccessList() { + return accessList; + } + + public void setAccessList(AccessList accessList) { + this.accessList = accessList; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getGasUsed() { + return gasUsed; + } + + public void setGasUsed(String gasUsed) { + this.gasUsed = gasUsed; + } + + public static class AccessListResultDeserializer extends JsonDeserializer { + @Override + public AccessListResultData deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode node = p.getCodec().readTree(p); + + AccessList accessList = new AccessList(); + JsonNode accessListNode = node.get("accessList"); + for (JsonNode innerNode : accessListNode) { + AccessTuple accessTuple = objectMapper.treeToValue(innerNode, AccessTuple.class); + accessList.add(accessTuple); + } + + AccessListResultData accessListResultData = new AccessListResultData(accessList, null, "0x0"); + JsonNode error = node.get("error"); + if (error != null) { + accessListResultData.setError(error.textValue()); + } + JsonNode gasUsed = node.get("gasUsed"); + if (gasUsed != null) { + accessListResultData.setGasUsed(gasUsed.textValue()); + } + return accessListResultData; + } + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/methods/response/Block.java b/core/src/main/java/com/klaytn/caver/methods/response/Block.java index e975bcff6..51dba92dd 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/Block.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/Block.java @@ -133,10 +133,15 @@ public static class BlockData { */ private String voteData; + /** + * Base fee per gas + */ + private String baseFeePerGas; + public BlockData() { } - public BlockData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String totalBlockScore, String extraData, String size, String gasUsed, String timestamp, String timestampFoS, List transactions, String governanceData, String voteData) { + public BlockData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String totalBlockScore, String extraData, String size, String gasUsed, String timestamp, String timestampFoS, List transactions, String governanceData, String voteData, String baseFeePerGas) { this.number = number; this.hash = hash; this.parentHash = parentHash; @@ -155,6 +160,7 @@ public BlockData(String number, String hash, String parentHash, String logsBloom this.transactions = transactions; this.governanceData = governanceData; this.voteData = voteData; + this.baseFeePerGas = baseFeePerGas; } public String getNumber() { @@ -302,6 +308,14 @@ public String getTotalBlockScore() { public void setTotalBlockScore(String totalBlockScore) { this.totalBlockScore = totalBlockScore; } + + public String getBaseFeePerGas() { + return baseFeePerGas; + } + + public void setBaseFeePerGas(String baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; + } } public static class ResponseDeserializer extends JsonDeserializer { diff --git a/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java new file mode 100644 index 000000000..a57abd9c0 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/methods/response/BlockHeader.java @@ -0,0 +1,268 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.methods.response; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.web3j.protocol.ObjectMapperFactory; +import org.web3j.protocol.core.Response; + +import java.io.IOException; + +public class BlockHeader extends Response { + + @Override + public void setResult(BlockHeaderData result) { + super.setResult(result); + } + + public static class BlockHeaderData { + /** + * The block number. null when its pending block + */ + private String number; + + /** + * Hash of the block. null when its pending block + */ + private String hash; + + /** + * Hash of the parent block + */ + private String parentHash; + + /** + * The bloom filter for the logs of the block. null when its pending block + */ + private String logsBloom; + + /** + * The root of the transaction trie of the block + */ + private String transactionsRoot; + + /** + * The root of the final state trie of the block + */ + private String stateRoot; + + /** + * The root of the receipts trie of the block + */ + private String receiptsRoot; + + /** + * The address of the beneficiary to whom the block rewards were given. + */ + private String reward; + + /** + * Former difficulty. Always 1 in the BFT consensus engine + */ + private String blockScore; + + /** + * The "extra data" field of this block + */ + private String extraData; + + /** + * The total used gas by all transactions in this block + */ + private String gasUsed; + + /** + * The Unix timestamp for when the block was collated + */ + private String timestamp; + + /** + * The fraction of a second of the timestamp for when the block was collated + */ + private String timestampFoS; + + /** + * RLP encoded governance configuration + */ + private String governanceData; + + /** + * Base fee per gas + */ + private String baseFeePerGas; + + /** + * RLP encoded governance vote of the proposer + */ + private String voteData; + + public BlockHeaderData() { + } + + public BlockHeaderData(String number, String hash, String parentHash, String logsBloom, String transactionsRoot, String stateRoot, String receiptsRoot, String reward, String blockScore, String extraData, String gasUsed, String timestamp, String timestampFoS, String governanceData, String baseFeePerGas, String voteData) { + this.number = number; + this.hash = hash; + this.parentHash = parentHash; + this.logsBloom = logsBloom; + this.transactionsRoot = transactionsRoot; + this.stateRoot = stateRoot; + this.receiptsRoot = receiptsRoot; + this.reward = reward; + this.blockScore = blockScore; + this.extraData = extraData; + this.gasUsed = gasUsed; + this.timestamp = timestamp; + this.timestampFoS = timestampFoS; + this.governanceData = governanceData; + this.baseFeePerGas = baseFeePerGas; + this.voteData = voteData; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getParentHash() { + return parentHash; + } + + public void setParentHash(String parentHash) { + this.parentHash = parentHash; + } + + public String getLogsBloom() { + return logsBloom; + } + + public void setLogsBloom(String logsBloom) { + this.logsBloom = logsBloom; + } + + public String getTransactionsRoot() { + return transactionsRoot; + } + + public void setTransactionsRoot(String transactionsRoot) { + this.transactionsRoot = transactionsRoot; + } + + public String getStateRoot() { + return stateRoot; + } + + public void setStateRoot(String stateRoot) { + this.stateRoot = stateRoot; + } + + public String getReceiptsRoot() { + return receiptsRoot; + } + + public void setReceiptsRoot(String receiptsRoot) { + this.receiptsRoot = receiptsRoot; + } + + public String getExtraData() { + return extraData; + } + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + public String getGasUsed() { + return gasUsed; + } + + public void setGasUsed(String gasUsed) { + this.gasUsed = gasUsed; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getTimestampFoS() { + return timestampFoS; + } + + public void setTimestampFoS(String timestampFoS) { + this.timestampFoS = timestampFoS; + } + + public String getGovernanceData() { + return governanceData; + } + + public void setGovernanceData(String governanceData) { + this.governanceData = governanceData; + } + + public String getReward() { + return reward; + } + + public void setReward(String reward) { + this.reward = reward; + } + + public String getBlockScore() { + return blockScore; + } + + public void setBlockScore(String blockScore) { + this.blockScore = blockScore; + } + + public String getBaseFeePerGas() { + return baseFeePerGas; + } + + public void setBaseFeePerGas(String baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; + } + + public String getVoteData() { + return voteData; + } + + public void setVoteData(String voteData) { + this.voteData = voteData; + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java new file mode 100644 index 000000000..2cdd52816 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/methods/response/FeeHistoryResult.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.methods.response; + +import org.web3j.protocol.core.Response; + +import java.util.List; + +public class FeeHistoryResult extends Response { + + @Override + public void setResult(FeeHistoryResultData result) { + super.setResult(result); + } + + public static class FeeHistoryResultData { + /** + * Lowest number block of returned range. + */ + private String oldestBlock; + + /** + * An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block.

+ * Zeroes are returned for pre-EIP-1559 blocks. + */ + private List baseFeePerGas; + + /** + * A two-dimensional array of effective priority fees per gas at the requested block percentiles. + */ + private List> reward; + + /** + * An array of gasUsed/gasLimit in the block. + */ + private List gasUsedRatio; + + public String getOldestBlock() { + return oldestBlock; + } + + public void setOldestBlock(String oldestBlock) { + this.oldestBlock = oldestBlock; + } + + public List getBaseFeePerGas() { + return baseFeePerGas; + } + + public void setBaseFeePerGas(List baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; + } + + public List> getReward() { + return reward; + } + + public void setReward(List> reward) { + this.reward = reward; + } + + public List getGasUsedRatio() { + return gasUsedRatio; + } + + public void setGasUsedRatio(List gasUsedRatio) { + this.gasUsedRatio = gasUsedRatio; + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java b/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java index ea9b5ea6a..32fe24e65 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/Transaction.java @@ -16,6 +16,7 @@ package com.klaytn.caver.methods.response; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -26,6 +27,7 @@ import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; import com.klaytn.caver.transaction.type.*; +import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.protocol.core.Response; @@ -86,6 +88,16 @@ public static class TransactionData { */ private String gasPrice; + /** + * Max priority fee per gas in peb. + */ + private String maxPriorityFeePerGas; + + /** + * Max fee per gas in peb. + */ + private String maxFeePerGas; + /** * Hash of the transaction. */ @@ -148,9 +160,20 @@ public static class TransactionData { */ private String value; + /** + * Chain ID. + */ + @JsonAlias({"chainId", "chainID"}) + private String chainID; + + /** + * An access list. + */ + private AccessList accessList; + public TransactionData() {} - public TransactionData(String blockHash, String blockNumber, String codeFormat, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String hash, boolean humanReadable, String key, String input, String nonce, String senderTxHash, List signatures, String to, String transactionIndex, String type, String typeInt, String value) { + public TransactionData(String blockHash, String blockNumber, String codeFormat, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String maxPriorityFeePerGas, String maxFeePerGas, String hash, boolean humanReadable, String key, String input, String nonce, String senderTxHash, List signatures, String to, String transactionIndex, String type, String typeInt, String value, String chainID, AccessList accesslist) { this.blockHash = blockHash; this.blockNumber = blockNumber; this.codeFormat = codeFormat; @@ -160,6 +183,8 @@ public TransactionData(String blockHash, String blockNumber, String codeFormat, this.from = from; this.gas = gas; this.gasPrice = gasPrice; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.maxFeePerGas = maxFeePerGas; this.hash = hash; this.humanReadable = humanReadable; this.key = key; @@ -172,6 +197,8 @@ public TransactionData(String blockHash, String blockNumber, String codeFormat, this.type = type; this.typeInt = typeInt; this.value = value; + this.chainID = chainID; + this.accessList = accesslist; } public String getBlockHash() { @@ -247,6 +274,22 @@ public void setGasPrice(String gasPrice) { this.gasPrice = gasPrice; } + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + public void setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + } + public String getHash() { return hash; } @@ -344,6 +387,25 @@ public void setKey(String key) { this.key = key; } + public String getChainID() { + if (chainID == null) { + return ""; + } + return chainID; + } + + public void setChainID(String chainID) { + this.chainID = chainID; + } + + public AccessList getAccessList() { + return accessList; + } + + public void setAccessList(AccessList accessList) { + this.accessList = accessList; + } + /** * Convert TransactionData to Caver's transaction instance. * @param klay The Klay instance to fill gasPrice, chainId and nonce fields when signing a transaction. @@ -352,49 +414,53 @@ public void setKey(String key) { public AbstractTransaction convertToCaverTransaction(Klay klay) { switch (TransactionType.valueOf(this.getType())) { case TxTypeLegacyTransaction: - return LegacyTransaction.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getInput(), this.getValue()); + return LegacyTransaction.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue()); case TxTypeValueTransfer: - return ValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue()); + return ValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue()); case TxTypeFeeDelegatedValueTransfer: - return FeeDelegatedValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue()); + return FeeDelegatedValueTransfer.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue()); case TxTypeFeeDelegatedValueTransferWithRatio: - return FeeDelegatedValueTransferWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue()); + return FeeDelegatedValueTransferWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue()); case TxTypeValueTransferMemo: - return ValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); + return ValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedValueTransferMemo: - return FeeDelegatedValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedValueTransferMemo.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedValueTransferMemoWithRatio: - return FeeDelegatedValueTransferMemoWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedValueTransferMemoWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); case TxTypeAccountUpdate: - return AccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); + return AccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); case TxTypeFeeDelegatedAccountUpdate: - return FeeDelegatedAccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); + return FeeDelegatedAccountUpdate.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); case TxTypeFeeDelegatedAccountUpdateWithRatio: - return FeeDelegatedAccountUpdateWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); + return FeeDelegatedAccountUpdateWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), Account.createFromRLPEncoding(this.getFrom(), this.getKey())); case TxTypeSmartContractDeploy: - return SmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); + return SmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); case TxTypeFeeDelegatedSmartContractDeploy: - return FeeDelegatedSmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); + return FeeDelegatedSmartContractDeploy.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); case TxTypeFeeDelegatedSmartContractDeployWithRatio: - return FeeDelegatedSmartContractDeployWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); + return FeeDelegatedSmartContractDeployWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput(), false, Numeric.toHexStringWithPrefix(CodeFormat.EVM)); case TxTypeSmartContractExecution: - return SmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); + return SmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedSmartContractExecution: - return FeeDelegatedSmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedSmartContractExecution.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getTo(), this.getValue(), this.getInput()); case TxTypeFeeDelegatedSmartContractExecutionWithRatio: - return FeeDelegatedSmartContractExecutionWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); + return FeeDelegatedSmartContractExecutionWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getTo(), this.getValue(), this.getInput()); case TxTypeCancel: - return Cancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures()); + return Cancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures()); case TxTypeFeeDelegatedCancel: - return FeeDelegatedCancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures()); + return FeeDelegatedCancel.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures()); case TxTypeFeeDelegatedCancelWithRatio: - return FeeDelegatedCancelWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio()); + return FeeDelegatedCancelWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio()); case TxTypeChainDataAnchoring: - return ChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getInput()); + return ChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getInput()); case TxTypeFeeDelegatedChainDataAnchoring: - return FeeDelegatedChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getInput()); + return FeeDelegatedChainDataAnchoring.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getInput()); case TxTypeFeeDelegatedChainDataAnchoringWithRatio: - return FeeDelegatedChainDataAnchoringWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), "", this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getInput()); + return FeeDelegatedChainDataAnchoringWithRatio.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getFeePayer(), this.getFeePayerSignatures(), this.getFeeRatio(), this.getInput()); + case TxTypeEthereumAccessList: + return EthereumAccessList.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getGasPrice(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue(), this.getAccessList()); + case TxTypeEthereumDynamicFee: + return EthereumDynamicFee.create(klay, this.getFrom(), this.getNonce(), this.getGas(), this.getMaxPriorityFeePerGas(), this.getMaxFeePerGas(), this.getChainID(), this.getSignatures(), this.getTo(), this.getInput(), this.getValue(), this.getAccessList()); default: throw new RuntimeException("Invalid transaction type : Cannot create a transaction instance that has Tx type :" + this.getType()); } diff --git a/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java b/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java index 39f4eb8f9..173bd7dab 100644 --- a/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java +++ b/core/src/main/java/com/klaytn/caver/methods/response/TransactionReceipt.java @@ -16,12 +16,14 @@ package com.klaytn.caver.methods.response; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.klaytn.caver.crypto.KlaySignatureData; +import com.klaytn.caver.transaction.utils.AccessList; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.protocol.core.Response; import org.web3j.utils.Numeric; @@ -132,6 +134,16 @@ public static class TransactionReceiptData { */ private String gasPrice; + /** + * Max priority fee per gas in peb. + */ + private String maxPriorityFeePerGas; + + /** + * Max fee per gas in peb. + */ + private String maxFeePerGas; + /** * The amount of gas used by this specific transaction alone. */ @@ -219,10 +231,21 @@ public static class TransactionReceiptData { */ private String value; + /** + * Chain ID. + */ + @JsonAlias({"chainId", "chainID"}) + private String chainID; + + /** + * An access list. + */ + private AccessList accessList; + public TransactionReceiptData() { } - public TransactionReceiptData(String blockHash, String blockNumber, String codeFormat, String contractAddress, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String gasUsed, boolean humanReadable, String key, String input, List logs, String logsBloom, String nonce, String senderTxHash, List signatures, String status, String to, String transactionIndex, String transactionHash, String txError, String type, String typeInt, String value) { + public TransactionReceiptData(String blockHash, String blockNumber, String codeFormat, String contractAddress, String feePayer, List feePayerSignatures, String feeRatio, String from, String gas, String gasPrice, String maxPriorityFeePerGas, String maxFeePerGas, String gasUsed, boolean humanReadable, String key, String input, List logs, String logsBloom, String nonce, String senderTxHash, List signatures, String status, String to, String transactionIndex, String transactionHash, String txError, String type, String typeInt, String value, String chainID, AccessList accessList) { this.blockHash = blockHash; this.blockNumber = blockNumber; this.codeFormat = codeFormat; @@ -233,6 +256,8 @@ public TransactionReceiptData(String blockHash, String blockNumber, String codeF this.from = from; this.gas = gas; this.gasPrice = gasPrice; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.maxFeePerGas = maxFeePerGas; this.gasUsed = gasUsed; this.humanReadable = humanReadable; this.key = key; @@ -250,6 +275,8 @@ public TransactionReceiptData(String blockHash, String blockNumber, String codeF this.type = type; this.typeInt = typeInt; this.value = value; + this.chainID = chainID; + this.accessList = accessList; } public String getBlockHash() { @@ -333,6 +360,22 @@ public void setGasPrice(String gasPrice) { this.gasPrice = gasPrice; } + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + public void setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + } + public String getGasUsed() { return gasUsed; } @@ -469,6 +512,22 @@ public String getValue() { public void setValue(String value) { this.value = value; } + + public String getChainID() { + return chainID; + } + + public void setChainID(String chainID) { + this.chainID = chainID; + } + + public AccessList getAccessList() { + return accessList; + } + + public void setAccessList(AccessList accessList) { + this.accessList = accessList; + } } public static class SignatureDataListDeserializer extends JsonDeserializer> { diff --git a/core/src/main/java/com/klaytn/caver/rpc/Klay.java b/core/src/main/java/com/klaytn/caver/rpc/Klay.java index a72777414..f992b05b4 100644 --- a/core/src/main/java/com/klaytn/caver/rpc/Klay.java +++ b/core/src/main/java/com/klaytn/caver/rpc/Klay.java @@ -34,8 +34,10 @@ import org.web3j.protocol.core.DefaultBlockParameterNumber; import org.web3j.protocol.core.Request; +import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; +import java.util.List; public class Klay { @@ -406,6 +408,143 @@ public Request getBlockNumber() { Quantity.class); } + /** + * Returns a block header by block hash. + *

Example:
+     * {@code
+     *  String blockHash = "0x5f06bed1f3f11d4f2b0760cfdf95ce6b2e6431ca46e2b778f2b958d4e5b9aa43";
+     *  BlockHeader response = caver.rpc.klay.getHeaderByHash(blockHash);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockHash The hash of block. + * @return BlockHeader + */ + public Request getHeaderByHash(String blockHash) { + return new Request<>( + "klay_getHeaderByHash", + Arrays.asList(blockHash), + web3jService, + BlockHeader.class); + } + + /** + * Returns a block header by block number. + *
Example:
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(5));
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockNumber The block number. + * @return BlockHeader + */ + public Request getHeaderByNumber(BigInteger blockNumber) { + DefaultBlockParameterNumber blockParameterNumber = new DefaultBlockParameterNumber(blockNumber); + return getHeaderByNumber(blockParameterNumber); + } + + /** + * Returns a block header by block tag. + *
Example:
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockTag The string "latest", "earliest" or "pending" + * @return BlockHeader + */ + public Request getHeaderByNumber(DefaultBlockParameterName blockTag) { + return getHeaderByNumber(DefaultBlockParameter.valueOf(blockTag.getValue())); + } + + /** + * Returns a block header by block number or block tag. + *
Example:
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     *
+     *  response = caver.rpc.klay.getHeaderByNumber(new DefaultBlockParameterNumber(0));
+     *  blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockNumberOrTag The block number or block tag which is one of "latest", "earliest", or "pending". + * @return BlockHeader + */ + public Request getHeaderByNumber(DefaultBlockParameter blockNumberOrTag) { + return new Request<>( + "klay_getHeaderByNumber", + Arrays.asList(blockNumberOrTag), + web3jService, + BlockHeader.class); + } + + /** + * Returns a block header by block hash. + *
Example:
+     * {@code
+     *  String blockHash = "0x5f06bed1f3f11d4f2b0760cfdf95ce6b2e6431ca46e2b778f2b958d4e5b9aa43";
+     *  BlockHeader response = caver.rpc.klay.getHeader(blockHash);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockHash The hash of block. + * @return BlockHeader + */ + public Request getHeader(String blockHash) { + return getHeaderByHash(blockHash); + } + + /** + * Returns a block header by block number. + *
Example:
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(5));
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockNumber The block number. + * @return BlockHeader + */ + public Request getHeader(BigInteger blockNumber) { + return getHeaderByNumber(blockNumber); + } + + /** + * Returns a block header by block tag. + *
Example:
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeader(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockTag The string "latest", "earliest" or "pending" + * @return BlockHeader + */ + public Request getHeader(DefaultBlockParameterName blockTag) { + return getHeaderByNumber(DefaultBlockParameter.valueOf(blockTag.getValue())); + } + + /** + * Returns a block header by block number or block tag. + *
Example:
+     * {@code
+     *  BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST);
+     *  BlockHeader.BlockHeaderData blockHeaderData = response.getResult();
+     *
+     *  response = caver.rpc.klay.getHeaderByNumber(new DefaultBlockParameterNumber(0));
+     *  blockHeaderData = response.getResult();
+     * }
+     * 
+ * @param blockNumberOrTag The block number or block tag which is one of "latest", "earliest", or "pending". + * @return BlockHeader + */ + public Request getHeader(DefaultBlockParameter blockNumberOrTag) { + return getHeaderByNumber(blockNumberOrTag); + } + /** * Returns information about a block by block number.

* It set "isFullTransaction" param to false. @@ -1137,6 +1276,27 @@ public Request getGasPriceAt() { ); } + /** + * Returns a suggestion for a gas tip cap for dynamic fee transactions in peb.

+ * Note: Since Klaytn has a fixed gas price, this `caver.rpc.klay.getMaxPriorityFeePerGas` returns the gas price set by Klaytn. + *

 Example : 
+     * {@code
+     *
+     * Quantity response = caver.rpc.klay.getMaxPriorityFeePerGas().send();
+     * BigInteger result = response.getValue();
+     *
+     * }
+     * 
+ * @return Quantity + */ + public Request getMaxPriorityFeePerGas() { + return new Request<>( + "klay_maxPriorityFeePerGas", + Collections.emptyList(), + web3jService, + Quantity.class); + } + /** * Returns the unit price of the given block in peb.

* NOTE: This API has different behavior from Ethereum's and returns a gas price of Klaytn instead of suggesting a gas price as in Ethereum. @@ -1321,6 +1481,143 @@ public Request sha3(String data) { Bytes.class); } + + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *

Example :
+     * {@code
+     *
+     * BigInteger blockNumber = BigInteger.valueOf(5);
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockNumber The block number. + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, BigInteger blockNumber) { + return createAccessList(callObject, new DefaultBlockParameterNumber(blockNumber)); + } + + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *
Example:
+     * {@code
+     *
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockTag The string "latest", "earliest" or "pending" + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, DefaultBlockParameterName blockTag) { + return createAccessList(callObject, DefaultBlockParameter.valueOf(blockTag.getValue())); + } + + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *
Example:
+     * {@code
+     *
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send();
+     *
+     * accessListResult = caver.rpc.klay.createAccessList(callObject, new DefaultBlockParameterNumber(1)).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockNumberOrTag The block number or block tag which is one of "latest", "earliest", or "pending". + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, DefaultBlockParameter blockNumberOrTag) { + if (blockNumberOrTag == null) { + blockNumberOrTag = DefaultBlockParameterName.LATEST; + } + return new Request<>( + "klay_createAccessList", + Arrays.asList(callObject, blockNumberOrTag), + web3jService, + AccessListResult.class + ); + } + + /** + * Returns a list of addresses and storage keys used by the transaction, plus the gas consumed when the access list is added. + *
Example:
+     * {@code
+     *
+     * String blockHash = "0x421440aef6024e2da883eadf663b9b485fe1c14f02883541fa4e6c16f7be8c74";
+     * AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, blockHash).send();
+     *
+     * }
+     * 
+ * @param callObject The transaction call object. + * @param blockHash The block hash. + * @return AccessListResult + */ + public Request createAccessList(CallObject callObject, String blockHash) { + return new Request<>( + "klay_createAccessList", + Arrays.asList(callObject, blockHash), + web3jService, + AccessListResult.class + ); + } + + + /** + * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. + *
Example:
+     * {@code
+     *
+     * long blockCount = 5;
+     * // Use block number from Transaction Receipt data.
+     * long lastBlock = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue();
+     * List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
+     * FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, lastBlock, rewardPercentiles).send();
+     *
+     * }
+     * 
+ * @param blockCount Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. + * @param lastBlock Highest number block (or block tag string) of the requested range. + * @param rewardPercentiles A monotonically increasing list of percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used. (Example: `['0', '25', '50', '75', '100']` or `['0', '0.5', '1', '1.5', '3', '80']`) + * @return + */ + public Request getFeeHistory(long blockCount, long lastBlock, List rewardPercentiles) { + return new Request<>( + "klay_feeHistory", + Arrays.asList(blockCount, lastBlock, rewardPercentiles), + web3jService, + FeeHistoryResult.class); + } + + /** + * Returns fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available. + *
Example:
+     * {@code
+     *
+     * long blockCount = 5;
+     * List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f));
+     * FeeHistory feeHistory = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send();
+     *
+     * }
+     * 
+ * @param blockCount Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. + * @param lastBlock Highest number block (or block tag string) of the requested range. + * @param rewardPercentiles A monotonically increasing list of percentile values to sample from each block’s effective priority fees per gas in ascending order, weighted by gas used. (Example: `['0', '25', '50', '75', '100']` or `['0', '0.5', '1', '1.5', '3', '80']`) + * @return + */ + public Request getFeeHistory(long blockCount, DefaultBlockParameter lastBlock, List rewardPercentiles) { + return new Request<>( + "klay_feeHistory", + Arrays.asList(blockCount, lastBlock, rewardPercentiles), + web3jService, + FeeHistoryResult.class); + } + /** * Creates a new subscription to specific events by using RPC Pub/Sub over Websockets.

* diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java index 7028e55bb..79d060309 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedTransaction.java @@ -102,14 +102,13 @@ public AbstractFeeDelegatedTransaction(Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A Signature list * @param feePayer The address of the fee payer. * @param feePayerSignatures The fee payers's signatures. */ - public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures) { - super(klaytnCall, type, from, nonce, gas, gasPrice, chainId, signatures); + public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures) { + super(klaytnCall, type, from, nonce, gas, chainId, signatures); setFeePayer(feePayer); setFeePayerSignatures(feePayerSignatures); } @@ -238,48 +237,6 @@ public void appendFeePayerSignatures(List signatureData) { setFeePayerSignatures(signatureData); } - /** - * Combines signatures and feePayerSignatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. - * When combining the signatures into a transaction instance, - * an error is thrown if the decoded transaction contains different value except signatures. - * @param rlpEncoded A List of RLP-encoded transaction strings. - * @return String - */ - public String combineSignedRawTransactions(List rlpEncoded) { - boolean fillVariable = false; - - // If the signatures are empty, there may be an undefined member variable. - // In this case, the empty information is filled with the decoded result. - // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. - if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; - - for(String encodedStr : rlpEncoded) { - AbstractFeeDelegatedTransaction txObj = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); - - if(fillVariable) { - if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); - if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); - if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { - if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { - this.setFeePayer(txObj.getFeePayer()); - fillVariable = false; - } - } - } - - // Signatures can only be combined for the same transaction. - // Therefore, compare whether the decoded transaction is the same as this. - if(!this.compareTxField(txObj, false)) { - throw new RuntimeException("Transactions containing different information cannot be combined."); - } - - this.appendSignatures(txObj.getSignatures()); - this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); - } - - return this.getRLPEncoding(); - } - /** * Returns a RLP-encoded transaction string for making fee payer's signature. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java index 79b6b0ff1..6a2aea843 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractFeeDelegatedWithRatioTransaction.java @@ -74,15 +74,14 @@ public AbstractFeeDelegatedWithRatioTransaction(Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A signature list * @param feePayer The address of the fee payer. * @param feePayerSignatures The fee payers's signatures. * @param feeRatio A fee ratio of the fee payer. */ - public AbstractFeeDelegatedWithRatioTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { - super(klaytnCall, type, from, nonce, gas, gasPrice, chainId, signatures, feePayer, feePayerSignatures); + public AbstractFeeDelegatedWithRatioTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { + super(klaytnCall, type, from, nonce, gas, chainId, signatures, feePayer, feePayerSignatures); setFeeRatio(feeRatio); } diff --git a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java index e8390837f..a0d2a187c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/AbstractTransaction.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.AccountKeyRoleBased; -import com.klaytn.caver.transaction.type.LegacyTransaction; import com.klaytn.caver.transaction.type.TransactionType; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.AbstractKeyring; @@ -72,11 +71,6 @@ abstract public class AbstractTransaction { */ private String gas; - /** - * A unit price of gas in peb the sender will pay for a transaction fee. - */ - private String gasPrice = "0x"; - /** * Network ID */ @@ -97,7 +91,6 @@ public static class Builder { private String from; private String nonce = "0x"; - private String gasPrice = "0x"; private String chainId = "0x"; private Klay klaytnCall = null; private List signatures = new ArrayList<>(); @@ -131,16 +124,6 @@ public B setGas(BigInteger gas) { return (B) this; } - public B setGasPrice(String gasPrice) { - this.gasPrice = gasPrice; - return (B) this; - } - - public B setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - return (B) this; - } - public B setChainId(String chainId) { this.chainId = chainId; return (B) this; @@ -181,7 +164,6 @@ public AbstractTransaction(AbstractTransaction.Builder builder) { builder.from, builder.nonce, builder.gas, - builder.gasPrice, builder.chainId, builder.signatures ); @@ -194,16 +176,14 @@ public AbstractTransaction(AbstractTransaction.Builder builder) { * @param from The address of the sender. * @param nonce A value used to uniquely identify a sender’s transaction. * @param gas The maximum amount of gas the transaction is allowed to use. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. * @param chainId Network ID * @param signatures A Signature list */ - public AbstractTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String gasPrice, String chainId, List signatures) { + public AbstractTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures) { setKlaytnCall(klaytnCall); setType(type); setFrom(from); setNonce(nonce); - setGasPrice(gasPrice); setGas(gas); setChainId(chainId); setSignatures(signatures); @@ -268,8 +248,8 @@ public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { * @throws IOException */ public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { - if(this.getType().equals(TransactionType.TxTypeLegacyTransaction.toString()) && keyring.isDecoupled()) { - throw new IllegalArgumentException("A legacy transaction cannot be signed with a decoupled keyring."); + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } if(this.from.equals("0x") || this.from.equals(Utils.DEFAULT_ZERO_ADDRESS)){ @@ -312,8 +292,8 @@ public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOExc * @throws IOException */ public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { - if(this.getType().equals(TransactionType.TxTypeLegacyTransaction.toString()) && keyring.isDecoupled()) { - throw new IllegalArgumentException("A legacy transaction cannot be signed with a decoupled keyring."); + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); } if(this.from.equals("0x") || this.from.equals(Utils.DEFAULT_ZERO_ADDRESS)){ @@ -361,33 +341,8 @@ public void appendSignatures(List signatureData) { * @param rlpEncoded A List of RLP-encoded transaction strings. * @return String */ - public String combineSignedRawTransactions(List rlpEncoded) { - boolean fillVariable = false; - - // If the signatures are empty, there may be an undefined member variable. - // In this case, the empty information is filled with the decoded result. - if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + public abstract String combineSignedRawTransactions(List rlpEncoded); - for(String encodedStr : rlpEncoded) { - AbstractTransaction txObj = TransactionDecoder.decode(encodedStr); - - if(fillVariable) { - if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); - if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); - fillVariable = false; - } - - // Signatures can only be combined for the same transaction. - // Therefore, compare whether the decoded transaction is the same as this. - if(!this.compareTxField(txObj, false)) { - throw new RuntimeException("Transactions containing different information cannot be combined."); - } - - this.appendSignatures(txObj.getSignatures()); - } - - return this.getRLPEncoding(); - } /** * Returns a RawTransaction(RLP-encoded transaction string) @@ -446,15 +401,10 @@ public void fillTransaction() throws IOException{ if(this.chainId.equals("0x")) { this.chainId = klaytnCall.getChainID().send().getResult(); } - - if(this.gasPrice.equals("0x")) { - this.gasPrice = klaytnCall.getGasPrice().send().getResult(); - } - } - if(this.nonce.equals("0x") || this.chainId.equals("0x") || this.gasPrice.equals("0x")) { - throw new RuntimeException("Cannot fill transaction data.(nonce, chainId, gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + if(this.nonce.equals("0x") || this.chainId.equals("0x")) { + throw new RuntimeException("Cannot fill transaction data.(nonce, chainId). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); } } @@ -469,7 +419,6 @@ public boolean compareTxField(AbstractTransaction txObj, boolean checkSig) { if(!this.getFrom().toLowerCase().equals(txObj.getFrom().toLowerCase())) return false; if(!Numeric.toBigInt(this.getNonce()).equals(Numeric.toBigInt(txObj.getNonce()))) return false; if(!Numeric.toBigInt(this.getGas()).equals(Numeric.toBigInt(txObj.getGas()))) return false; - if(!Numeric.toBigInt(this.getGasPrice()).equals(Numeric.toBigInt(txObj.getGasPrice()))) return false; if(checkSig) { List dataList = this.getSignatures(); @@ -494,10 +443,6 @@ public void validateOptionalValues(boolean checkChainID) { throw new RuntimeException("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); } - if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { - throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); - } - if(checkChainID) { if(this.getChainId() == null || this.getChainId().isEmpty() || this.getChainId().equals("0x")) { throw new RuntimeException("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); @@ -514,12 +459,12 @@ public void validateOptionalValues(boolean checkChainID) { * @return List<String> */ public List refineSignature(List signatureDataList) { - boolean isLegacy = this.getType().equals(TransactionType.TxTypeLegacyTransaction.toString()); + boolean isEthereumTransaction = TransactionHelper.isEthereumTransaction(this.getType()); List refinedList = SignatureData.refineSignature(signatureDataList); - if(isLegacy && refinedList.size() > 1) { - throw new RuntimeException("LegacyTransaction cannot have multiple signature."); + if(isEthereumTransaction && refinedList.size() > 1) { + throw new RuntimeException(this.getType() + " cannot have multiple signature."); } return refinedList; @@ -537,6 +482,11 @@ public List refineSignature(List signatureDataList */ public List recoverPublicKeys() { try { + // If it is EthereumTyped transaction(EthereumAccessList, EthereumDynamicFee), call recoverPublicKeysWithEthereumTypedTransaction. + if(TransactionHelper.isEthereumTypedTransaction(this.getType())) { + return recoverPublicKeysWithEthereumTypedTransaction(); + } + if(Utils.isEmptySig(this.getSignatures())) { throw new RuntimeException("Failed to recover public keys from signatures: signatures is empty."); } @@ -565,6 +515,23 @@ public List recoverPublicKeys() { } } + private List recoverPublicKeysWithEthereumTypedTransaction() throws SignatureException{ + if(Utils.isEmptySig(this.getSignatures())) { + throw new RuntimeException("Failed to recover public keys from signatures: signatures is empty."); + } + + String sigHash = TransactionHasher.getHashForSignature(this); + + List publicKeyList = new ArrayList<>(); + for(SignatureData signatureData : this.getSignatures()) { + if(Numeric.toBigInt(signatureData.getV()).compareTo(BigInteger.ZERO) != 0 && Numeric.toBigInt(signatureData.getV()).compareTo(BigInteger.ONE) != 0) { + throw new RuntimeException("Invalid Signature data : the v value must have 0 or 1."); + } + publicKeyList.add(Utils.recoverPublicKey(sigHash, signatureData, true)); + } + return publicKeyList; + } + /** * Getter function for klaytnRPC * @return Klay @@ -613,19 +580,10 @@ public String getGas() { return gas; } - /** - * Getter function for gas price - * @return String - */ - public String getGasPrice() { - return gasPrice; - } - /** * Getter function for chain id * @return String */ - @JsonIgnore public String getChainId() { return chainId; } @@ -647,8 +605,8 @@ public void setType(String type) { } public void setFrom(String from) { - //"From" field in LegacyTransaction allows null - if(this instanceof LegacyTransaction) { + //"From" field in EthereumTransaction allows null + if(TransactionHelper.isEthereumTransaction(this.getType())) { if(from == null || from.isEmpty() || from.equals("0x") || from.equals(Utils.DEFAULT_ZERO_ADDRESS)) from = Utils.DEFAULT_ZERO_ADDRESS; } else { if(from == null) { @@ -711,30 +669,6 @@ public void setNonce(BigInteger nonce) { setNonce(Numeric.toHexStringWithPrefix(nonce)); } - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(String gasPrice) { - if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { - gasPrice = "0x"; - } - - if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { - throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); - } - - this.gasPrice = gasPrice; - } - - /** - * Setter function for gas price. - * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. - */ - public void setGasPrice(BigInteger gasPrice) { - setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); - } - /** * Setter function for chain id. * @param chainId A network id. diff --git a/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java b/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java index c4e2dd116..fbb37f70c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TransactionDecoder.java @@ -70,6 +70,10 @@ public static AbstractTransaction decode(String rlpEncoded) { return FeeDelegatedValueTransferMemoWithRatio.decode(rlpBytes); } else if(rlpBytes[0] == TransactionType.TxTypeFeeDelegatedSmartContractDeployWithRatio.getType()) { return FeeDelegatedSmartContractDeployWithRatio.decode(rlpBytes); + } else if ((rlpBytes[0] << 8 | rlpBytes[1]) == TransactionType.TxTypeEthereumAccessList.getType()) { + return EthereumAccessList.decode(rlpBytes); + } else if ((rlpBytes[0] << 8 | rlpBytes[1]) == TransactionType.TxTypeEthereumDynamicFee.getType()) { + return EthereumDynamicFee.decode(rlpBytes); } else { return LegacyTransaction.decode(rlpBytes); diff --git a/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java b/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java index 142e6db5d..c0e11911a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TransactionHelper.java @@ -18,10 +18,15 @@ import com.klaytn.caver.methods.response.Transaction; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.type.TransactionType; import java.io.IOException; import java.util.List; +import static com.klaytn.caver.transaction.type.TransactionType.TxTypeLegacyTransaction; +import static com.klaytn.caver.transaction.type.TransactionType.TxTypeEthereumAccessList; +import static com.klaytn.caver.transaction.type.TransactionType.TxTypeEthereumDynamicFee; + /** * This class is a helper class provides methods that handles Transaction object comfortably. */ @@ -84,4 +89,44 @@ public static List recoverFeePayerPublicKeys(String rawTx) { AbstractFeeDelegatedTransaction tx = (AbstractFeeDelegatedTransaction)TransactionDecoder.decode(rawTx); return tx.recoverFeePayerPublicKeys(); } + + /** + * Returns true if the tx type is EthereumTransaction. + * + * @param type Transaction type integer. + * @return + */ + public static boolean isEthereumTransaction(int type) { + if (type == TxTypeLegacyTransaction.getType() || type == TxTypeEthereumAccessList.getType() || type == TxTypeEthereumDynamicFee.getType()) { + return true; + } + return false; + } + /** + * Returns true if the tx type is EthereumTransaction. + * + * @param type Transaction type string. + * @return + */ + public static boolean isEthereumTransaction(String type) { + if(type.equals(TransactionType.TxTypeLegacyTransaction.toString()) || type.equals(TxTypeEthereumAccessList.toString()) || type.equals(TxTypeEthereumDynamicFee.toString())) { + return true; + } + return false; + } + + public static boolean isEthereumTypedTransaction(int type) { + if(type == TxTypeEthereumAccessList.getType() || type == TxTypeEthereumDynamicFee.getType()) { + return true; + } + return false; + } + + public static boolean isEthereumTypedTransaction(String type) { + if(type.equals(TxTypeEthereumAccessList.toString()) || type.equals(TxTypeEthereumDynamicFee.toString())) { + return true; + } + return false; + } + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java b/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java index 8ea620422..3e25f1247 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java +++ b/core/src/main/java/com/klaytn/caver/transaction/TxPropertyBuilder.java @@ -30,6 +30,22 @@ public static LegacyTransaction.Builder legacyTransaction() { return new LegacyTransaction.Builder(); } + /** + * Creates a Builder of EthereumAccessList + * @return EthereumAccessList.Builder + */ + public static EthereumAccessList.Builder ethereumAccessList() { + return new EthereumAccessList.Builder(); + } + + /** + * Creates a Builder of EthereumDynamicFee + * @return EthereumDynamicFee.Builder + */ + public static EthereumDynamicFee.Builder ethereumDynamicFee() { + return new EthereumDynamicFee.Builder(); + } + /** * Creates a Builder of ValueTransfer * @return ValueTransfer.Builder diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java index 78359635d..73410c34d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/AccountUpdate.java @@ -16,14 +16,18 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,6 +37,7 @@ * Represents an account update transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypeaccountupdate to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class AccountUpdate extends AbstractTransaction { /** @@ -40,11 +45,17 @@ public class AccountUpdate extends AbstractTransaction { */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * AccountUpdate Builder class */ public static class Builder extends AbstractTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeAccountUpdate.toString()); @@ -55,6 +66,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public AccountUpdate build() { return new AccountUpdate(this); } @@ -92,6 +113,7 @@ public static AccountUpdate create(Klay klaytnCall, String from, String nonce, S public AccountUpdate(AccountUpdate.Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -112,11 +134,43 @@ public AccountUpdate(Klay klaytnCall, String from, String nonce, String gas, Str from, nonce, gas, - gasPrice, chainId, signatures ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -241,10 +295,78 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getAccount().getRLPEncodingAccountKey().equals(txObj.getAccount().getRLPEncodingAccountKey())) { return false; } + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + AccountUpdate txObj = (AccountUpdate) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java index daf278680..7b22789fe 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/Cancel.java @@ -16,13 +16,17 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -32,16 +36,33 @@ * Represents a cancel transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypecancel to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class Cancel extends AbstractTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * Cancel Builder class */ public static class Builder extends AbstractTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeCancel.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public Cancel build() { return new Cancel(this); } @@ -77,6 +98,7 @@ public static Cancel create(Klay klaytnCall, String from, String nonce, String g */ public Cancel(Cancel.Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -90,9 +112,51 @@ public Cancel(Cancel.Builder builder) { * @param signatures A Signature list */ public Cancel(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures) { - super(klaytnCall, TransactionType.TxTypeCancel.toString(), from, nonce, gas, gasPrice, chainId, signatures); + super( + klaytnCall, + TransactionType.TxTypeCancel.toString(), + from, + nonce, + gas, + chainId, + signatures + ); + setGasPrice(gasPrice); } + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded Cancel string. * @param rlpEncoded RLP-encoded Cancel string @@ -202,7 +266,76 @@ public String getCommonRLPEncodingForSignature() { public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!super.compareTxField(obj, checkSig)) return false; if(!(obj instanceof Cancel)) return false; + Cancel txObj = (Cancel) obj; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + Cancel txObj = (Cancel) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java index ee79992f3..0c4b5fa47 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ChainDataAnchoring.java @@ -16,14 +16,17 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,17 +36,24 @@ * Represents a chain data anchoring transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypechaindataanchoring to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class ChainDataAnchoring extends AbstractTransaction { /** * Data of the service chain. */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ChainDataAnchoring Builder class */ public static class Builder extends AbstractTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeChainDataAnchoring.toString()); @@ -54,6 +64,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public ChainDataAnchoring build() { return new ChainDataAnchoring(this); } @@ -91,6 +111,7 @@ public static ChainDataAnchoring create(Klay klaytnCall, String from, String non public ChainDataAnchoring(ChainDataAnchoring.Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -105,10 +126,52 @@ public ChainDataAnchoring(ChainDataAnchoring.Builder builder) { * @param input Data of the service chain. */ public ChainDataAnchoring(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String input) { - super(klaytnCall, TransactionType.TxTypeChainDataAnchoring.toString(), from, nonce, gas, gasPrice, chainId, signatures); + super( + klaytnCall, + TransactionType.TxTypeChainDataAnchoring.toString(), + from, + nonce, + gas, + chainId, + signatures + ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded ChainDataAnchoring string. * @param rlpEncoded RLP-encoded ChainDataAnchoring string @@ -226,10 +289,79 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { ChainDataAnchoring txObj = (ChainDataAnchoring)obj; if(!this.getInput().equals(txObj.getInput())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + ChainDataAnchoring txObj = (ChainDataAnchoring) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java new file mode 100644 index 000000000..0269a7846 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumAccessList.java @@ -0,0 +1,733 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.type; + +import com.klaytn.caver.account.AccountKeyRoleBased; +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; +import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TransactionHelper; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.KeyringFactory; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.web3j.crypto.Hash; +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Represents an ethereum access list transaction. + */ +public class EthereumAccessList extends AbstractTransaction { + /** + * The account address that will receive the transferred value. + */ + String to = "0x"; + + /** + * Data attached to the transaction, used for transaction execution. + */ + String input = "0x"; + + /** + * The amount of KLAY in peb to be transferred. + */ + String value = "0x00"; + + /** + * Access list is an EIP-2930 access list. + */ + AccessList accessList; + + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + + /** + * EthereumAccessList Builder class + */ + public static class Builder extends AbstractTransaction.Builder { + private String to = "0x"; + private String value = "0x0"; + private String input = "0x"; + private AccessList accessList = new AccessList(); + String gasPrice = "0x"; + + public Builder() { + super(TransactionType.TxTypeEthereumAccessList.toString()); + } + + public Builder setValue(String value) { + this.value = value; + return this; + } + + public Builder setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + return this; + } + + public Builder setInput(String input) { + this.input = input; + return this; + } + + public Builder setTo(String to) { + this.to = to; + return this; + } + + public Builder setAccessList(AccessList accessList) { + this.accessList = accessList; + return this; + } + + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + + public EthereumAccessList build() { + return new EthereumAccessList(this); + } + } + + /** + * Creates a EthereumAccessList instance. + * + * @param builder EthereumAccessList.Builder instance. + * @return EthereumAccessList + */ + public static EthereumAccessList create(EthereumAccessList.Builder builder) { + return new EthereumAccessList(builder); + } + + /** + * Create a EthereumAccessList instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumAccessList + */ + public static EthereumAccessList create(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return new EthereumAccessList(klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); + } + + /** + * Creates a EthereumAccessList instance. + * + * @param builder EthereumAccessList.Builder instance. + */ + public EthereumAccessList(EthereumAccessList.Builder builder) { + super(builder); + + setTo(builder.to); + setValue(builder.value); + setInput(builder.input); + setAccessList(builder.accessList); + setGasPrice(builder.gasPrice); + } + + + /** + * Create a EthereumAccessList instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + */ + public EthereumAccessList(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + super( + klaytnCall, + TransactionType.TxTypeEthereumAccessList.toString(), + from, + nonce, + gas, + chainId, + signatures + ); + setTo(to); + setValue(value); + setInput(input); + setAccessList(accessList); + setGasPrice(gasPrice); + } + + /** + * Returns the RLP-encoded string of this transaction (i.e., rawTransaction). + * @return String + */ + @Override + public String getRLPEncoding() { + // TransactionPayload = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGasPrice()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + rlpTypeList.add(this.getAccessList().toRlpList()); + SignatureData signatureData = this.getSignatures().get(0); + rlpTypeList.addAll(signatureData.toRlpList().getValues()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = Numeric.toBytesPadded(BigInteger.valueOf(TransactionType.TxTypeEthereumAccessList.getType()), 2); + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * @return String + */ + @Override + public String getCommonRLPEncodingForSignature() { + return getRLPEncodingForSignature(); + } + + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * + * @return String + */ + @Override + public String getRLPEncodingForSignature() { + // SigRLP = 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]) + + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGasPrice()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + rlpTypeList.add(this.getAccessList().toRlpList()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = new byte[]{(byte) TransactionType.TxTypeEthereumAccessList.getType()}; + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + /** + * Check equals txObj passed parameter and Current instance. + * + * @param obj The AbstractTransaction Object to compare + * @param checkSig Check whether signatures field is equal. + * @return boolean + */ + @Override + public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { + if (!super.compareTxField(obj, checkSig)) return false; + if (!(obj instanceof EthereumAccessList)) return false; + EthereumAccessList txObj = (EthereumAccessList) obj; + + if (!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; + if (!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if (!this.getInput().equals(txObj.getInput())) return false; + if (!this.getAccessList().equals(txObj.getAccessList())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; + + return true; + } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + EthereumAccessList txObj = (EthereumAccessList) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** + * Decodes a RLP-encoded EthereumAccessList string. + * + * @param rlpEncoded RLP-encoded EthereumAccessList string + * @return EthereumAccessList + */ + public static EthereumAccessList decode(String rlpEncoded) { + return decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Decodes a RLP-encoded EthereumAccessList byte array. + * + * @param rlpEncoded RLP-encoded EthereumAccessList byte array. + * @return EthereumAccessList + */ + public static EthereumAccessList decode(byte[] rlpEncoded) { + // TxHashRLP = 0x7801 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + try { + if ((rlpEncoded[0] << 8 | rlpEncoded[1]) != TransactionType.TxTypeEthereumAccessList.getType()) { + throw new IllegalArgumentException("Invalid RLP-encoded tag - " + TransactionType.TxTypeEthereumAccessList.toString()); + } + byte[] detachedType = Arrays.copyOfRange(rlpEncoded, 2, rlpEncoded.length); + RlpList rlpList = RlpDecoder.decode(detachedType); + List values = ((RlpList) rlpList.getValues().get(0)).getValues(); + + BigInteger chainId = ((RlpString) values.get(0)).asPositiveBigInteger(); + BigInteger nonce = ((RlpString) values.get(1)).asPositiveBigInteger(); + BigInteger gasPrice = ((RlpString) values.get(2)).asPositiveBigInteger(); + BigInteger gas = ((RlpString) values.get(3)).asPositiveBigInteger(); + String to = ((RlpString) values.get(4)).asString(); + BigInteger value = ((RlpString) values.get(5)).asPositiveBigInteger(); + String input = ((RlpString) values.get(6)).asString(); + + AccessList accessList = AccessList.decode((RlpList) values.get(7)); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setFrom(null) + .setInput(input) + .setValue(value) + .setChainId(chainId) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setNonce(nonce) + .setTo(to) + .setAccessList(accessList) + .build(); + + byte[] v = ((RlpString) values.get(8)).getBytes(); + byte[] r = ((RlpString) values.get(9)).getBytes(); + byte[] s = ((RlpString) values.get(10)).getBytes(); + SignatureData signatureData = new SignatureData(v, r, s); + + ethereumAccessList.appendSignatures(signatureData); + return ethereumAccessList; + } catch (Exception e) { + throw new RuntimeException("There is an error while decoding process."); + } + } + + /** + * Appends signatures array to transaction. + * EthereumAccessList transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData SignatureData instance contains ECDSA signature data + */ + @Override + public void appendSignatures(SignatureData signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures().get(0))) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + } + + if (!Utils.isEmptySig(signatureData)) { + int v = Integer.decode(signatureData.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + } + super.appendSignatures(signatureData); + } + + /** + * Appends signatures array to transaction. + * EthereumAccessList transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData List of SignatureData contains ECDSA signature data + */ + @Override + public void appendSignatures(List signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures())) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + } + + if(signatureData.size() != 1) { + throw new RuntimeException("Signatures are too long " + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + } + + SignatureData signature = signatureData.get(0); + if (!Utils.isEmptySig(signature)) { + int v = Integer.decode(signature.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + } + + super.appendSignatures(signatureData); + } + + /** + * Returns a hash string of transaction + * @return String + */ + @Override + public String getTransactionHash() { + // TxHashRLP = 0x01 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + String rlpEncoded = this.getRLPEncoding(); + byte[] rlpEncodedBytes = Numeric.hexStringToByteArray(rlpEncoded); + byte[] detachedType = Arrays.copyOfRange(rlpEncodedBytes, 1, rlpEncodedBytes.length); + return Hash.sha3(Numeric.toHexString(detachedType)); + } + + /** + * Signs to the transaction with a single private key. + * It sets Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyString The private key string. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(String keyString) throws IOException { + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * @param keyring The Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + if(this.getFrom().equals("0x") || this.getFrom().equals(Utils.DEFAULT_ZERO_ADDRESS) || this.getFrom().isEmpty()){ + this.setFrom(keyring.getAddress()); + } + + if(!this.getFrom().toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + List sigList = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + + this.appendSignatures(sigList); + + return this; + } + + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + /** + * Signs to the transaction with a single private key. + * @param keyString The private key string + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(String keyString, Function signer) throws IOException { + + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, signer); + } + + /** + * Signs to the transaction with a private key in the Keyring instance. + * It sets signer to TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOException { + return this.sign(keyring, index, TransactionHasher::getHashForSignature); + } + + /** + * Signs to the transaction with a private key in the Keyring instance. + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + if(this.getFrom().equals("0x") || this.getFrom().equals(Utils.DEFAULT_ZERO_ADDRESS) || this.getFrom().isEmpty()){ + this.setFrom(keyring.getAddress()); + } + + if(!this.getFrom().toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + SignatureData signatureData = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex(), index); + + this.appendSignatures(signatureData); + + return this; + } + + /** + * Getter function for to + * + * @return String + */ + public String getTo() { + return to; + } + + /** + * Setter function for to + * + * @param to The account address that will receive the transferred value. + */ + public void setTo(String to) { + // "to" field in EthereumAccessList allows null + if (to == null || to.isEmpty()) { + to = "0x"; + } + + if (!to.equals("0x") && !Utils.isAddress(to)) { + throw new IllegalArgumentException("Invalid address. : " + to); + } + this.to = to; + } + + /** + * Getter function for input + * + * @return String + */ + public String getInput() { + return input; + } + + /** + * Setter function for input + * + * @param input Data attached to the transaction. + */ + public void setInput(String input) { + if (input == null) { + throw new IllegalArgumentException("input is missing"); + } + + if (!Utils.isHex(input)) { + throw new IllegalArgumentException("Invalid input : " + input); + } + this.input = Numeric.prependHexPrefix(input); + } + + + /** + * Getter function for value + * + * @return String + */ + public String getValue() { + return value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(String value) { + if (value == null) { + throw new IllegalArgumentException("value is missing"); + } + + if (!Utils.isNumber(value)) { + throw new IllegalArgumentException("Invalid value : " + value); + } + this.value = value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + } + + /** + * Getter function for accessList + * + * @return accessList Access list is an EIP-2930 access list. + */ + public AccessList getAccessList() { + return accessList; + } + + /** + * Setter function for accessList + * + * @param accessList Access list is an EIP-2930 access list. + */ + public void setAccessList(AccessList accessList) { + if (accessList == null) { + accessList = new AccessList(); + } + this.accessList = accessList; + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java new file mode 100644 index 000000000..4cfba251d --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/EthereumDynamicFee.java @@ -0,0 +1,801 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.type; + +import com.klaytn.caver.account.AccountKeyRoleBased; +import com.klaytn.caver.methods.response.BlockHeader; +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; +import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TransactionHelper; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.KeyringFactory; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.web3j.crypto.Hash; +import org.web3j.protocol.core.DefaultBlockParameterName; +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Represents an ethereum access list transaction. + */ +public class EthereumDynamicFee extends AbstractTransaction { + /** + * The account address that will receive the transferred value. + */ + String to = "0x"; + + /** + * Data attached to the transaction, used for transaction execution. + */ + String input = "0x"; + + /** + * The amount of KLAY in peb to be transferred. + */ + String value = "0x00"; + + /** + * Access list is an EIP-2930 access list. + */ + AccessList accessList; + + /** + * A max priority fee per gas. + */ + String maxPriorityFeePerGas = "0x"; + + /** + * A max fee per gas. + */ + String maxFeePerGas = "0x"; + + /** + * EthereumDynamicFee Builder class + */ + public static class Builder extends AbstractTransaction.Builder { + private String to = "0x"; + private String value = "0x0"; + private String input = "0x"; + private AccessList accessList = new AccessList(); + private String maxPriorityFeePerGas = "0x"; + private String maxFeePerGas = "0x"; + + public Builder() { + super(TransactionType.TxTypeEthereumDynamicFee.toString()); + } + + public Builder setValue(String value) { + this.value = value; + return this; + } + + public Builder setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + return this; + } + + public Builder setInput(String input) { + this.input = input; + return this; + } + + public Builder setTo(String to) { + this.to = to; + return this; + } + + public Builder setAccessList(AccessList accessList) { + this.accessList = accessList; + return this; + } + + public Builder setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + return this; + } + + public Builder setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) { + setMaxPriorityFeePerGas(Numeric.toHexStringWithPrefix(maxPriorityFeePerGas)); + return this; + } + + public Builder setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + return this; + } + + public Builder setMaxFeePerGas(BigInteger maxFeePerGas) { + setMaxFeePerGas(Numeric.toHexStringWithPrefix(maxFeePerGas)); + return this; + } + + public EthereumDynamicFee build() { + return new EthereumDynamicFee(this); + } + } + + /** + * Creates a EthereumDynamicFee instance. + * + * @param builder EthereumDynamicFee.Builder instance. + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee create(EthereumDynamicFee.Builder builder) { + return new EthereumDynamicFee(builder); + } + + + /** + * Create a EthereumDynamicFee instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param maxPriorityFeePerGas Max priority fee per gas. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee create(Klay klaytnCall, String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return new EthereumDynamicFee(klaytnCall, from, nonce, gas, maxPriorityFeePerGas, maxFeePerGas, chainId, signatures, to, input, value, accessList); + } + + /** + * Creates a EthereumDynamicFee instance. + * + * @param builder EthereumDynamicFee.Builder instance. + */ + public EthereumDynamicFee(EthereumDynamicFee.Builder builder) { + super(builder); + + setTo(builder.to); + setValue(builder.value); + setInput(builder.input); + setAccessList(builder.accessList); + setMaxPriorityFeePerGas(builder.maxPriorityFeePerGas); + setMaxFeePerGas(builder.maxFeePerGas); + } + + /** + * Create a EthereumDynamicFee instance. + * + * @param klaytnCall Klay RPC instance + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param maxPriorityFeePerGas Max priority fee per gas. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + */ + public EthereumDynamicFee(Klay klaytnCall, String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + super( + klaytnCall, + TransactionType.TxTypeEthereumDynamicFee.toString(), + from, + nonce, + gas, + chainId, + signatures + ); + setTo(to); + setValue(value); + setInput(input); + setAccessList(accessList); + setMaxPriorityFeePerGas(maxPriorityFeePerGas); + setMaxFeePerGas(maxFeePerGas); + } + + /** + * Returns the RLP-encoded string of this transaction (i.e., rawTransaction). + * @return String + */ + @Override + public String getRLPEncoding() { + // TransactionPayload = 0x7802 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxPriorityFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + rlpTypeList.add(this.getAccessList().toRlpList()); + SignatureData signatureData = this.getSignatures().get(0); + rlpTypeList.addAll(signatureData.toRlpList().getValues()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = Numeric.toBytesPadded(BigInteger.valueOf(TransactionType.TxTypeEthereumDynamicFee.getType()), 2); + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * @return String + */ + @Override + public String getCommonRLPEncodingForSignature() { + return getRLPEncodingForSignature(); + } + + /** + * Returns the RLP-encoded string to make the signature of this transaction. + * + * @return String + */ + @Override + public String getRLPEncodingForSignature() { // SigRLP = 0x02 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]) + + this.validateOptionalValues(true); + + List rlpTypeList = new ArrayList<>(); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getNonce()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxPriorityFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getMaxFeePerGas()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getGas()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getTo()))); + rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getValue()))); + rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getInput()))); + rlpTypeList.add(this.getAccessList().toRlpList()); + + byte[] encodedTransaction = RlpEncoder.encode(new RlpList(rlpTypeList)); + byte[] type = new byte[]{(byte) TransactionType.TxTypeEthereumDynamicFee.getType()}; + byte[] rawTx = BytesUtils.concat(type, encodedTransaction); + + return Numeric.toHexString(rawTx); + } + + /** + * Check equals txObj passed parameter and Current instance. + * + * @param obj The AbstractTransaction Object to compare + * @param checkSig Check whether signatures field is equal. + * @return boolean + */ + @Override + public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { + if (!super.compareTxField(obj, checkSig)) return false; + if (!(obj instanceof EthereumDynamicFee)) return false; + EthereumDynamicFee txObj = (EthereumDynamicFee) obj; + + if (!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; + if (!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if (!this.getInput().equals(txObj.getInput())) return false; + if (!this.getAccessList().equals(txObj.getAccessList())) return false; + if(Numeric.toBigInt(this.getMaxPriorityFeePerGas()).compareTo(Numeric.toBigInt(txObj.getMaxPriorityFeePerGas())) != 0) return false; + if(Numeric.toBigInt(this.getMaxFeePerGas()).compareTo(Numeric.toBigInt(txObj.getMaxFeePerGas())) != 0) return false; + + return true; + } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + EthereumDynamicFee txObj = (EthereumDynamicFee) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getMaxPriorityFeePerGas().equals("0x")) this.setMaxPriorityFeePerGas(txObj.getMaxPriorityFeePerGas()); + if(this.getMaxFeePerGas().equals("0x")) this.setMaxFeePerGas(txObj.getMaxFeePerGas()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction fields.(maxPriorityFeePerGas and maxFeePerGas) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.getMaxPriorityFeePerGas().equals("0x")) { + this.setMaxPriorityFeePerGas(this.getKlaytnCall().getMaxPriorityFeePerGas().send().getResult()); + } + if(this.getMaxFeePerGas().equals("0x")) { + BlockHeader blockHeader = this.getKlaytnCall().getHeader(DefaultBlockParameterName.LATEST).send(); + BigInteger baseFee = Numeric.toBigInt(blockHeader.getResult().getBaseFeePerGas()); + BigInteger maxPriorityFeePerGas = Numeric.toBigInt(this.getMaxPriorityFeePerGas()); + this.setMaxFeePerGas(baseFee.multiply(BigInteger.valueOf(2)).add(maxPriorityFeePerGas)); + } + if(this.getMaxPriorityFeePerGas().equals("0x") || this.getMaxFeePerGas().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (maxPriorityFeePerGas, maxFeePerGas). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getMaxPriorityFeePerGas() == null || this.getMaxPriorityFeePerGas().isEmpty() || this.getMaxPriorityFeePerGas().equals("0x")) { + throw new RuntimeException("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + } + if(this.getMaxFeePerGas() == null || this.getMaxFeePerGas().isEmpty() || this.getMaxFeePerGas().equals("0x")) { + throw new RuntimeException("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee string. + * + * @param rlpEncoded RLP-encoded EthereumDynamicFee string + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee decode(String rlpEncoded) { + return decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee byte array. + * + * @param rlpEncoded RLP-encoded EthereumDynamicFee byte array. + * @return EthereumDynamicFee + */ + public static EthereumDynamicFee decode(byte[] rlpEncoded) { + // TxHashRLP = 0x7802 + encode([chainId, nonce, gasPrice, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + try { + if ((rlpEncoded[0] << 8 | rlpEncoded[1]) != TransactionType.TxTypeEthereumDynamicFee.getType()) { + throw new IllegalArgumentException("Invalid RLP-encoded tag - " + TransactionType.TxTypeEthereumDynamicFee.toString()); + } + byte[] detachedType = Arrays.copyOfRange(rlpEncoded, 2, rlpEncoded.length); + RlpList rlpList = RlpDecoder.decode(detachedType); + List values = ((RlpList) rlpList.getValues().get(0)).getValues(); + + BigInteger chainId = ((RlpString) values.get(0)).asPositiveBigInteger(); + BigInteger nonce = ((RlpString) values.get(1)).asPositiveBigInteger(); + BigInteger maxPriorityFeePerGas = ((RlpString) values.get(2)).asPositiveBigInteger(); + BigInteger maxFeePerGas = ((RlpString) values.get(3)).asPositiveBigInteger(); + BigInteger gas = ((RlpString) values.get(4)).asPositiveBigInteger(); + String to = ((RlpString) values.get(5)).asString(); + BigInteger value = ((RlpString) values.get(6)).asPositiveBigInteger(); + String input = ((RlpString) values.get(7)).asString(); + + AccessList accessList = AccessList.decode((RlpList) values.get(8)); + + EthereumDynamicFee ethereumAccessList = new EthereumDynamicFee.Builder() + .setFrom(null) + .setInput(input) + .setValue(value) + .setChainId(chainId) + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setNonce(nonce) + .setTo(to) + .setAccessList(accessList) + .build(); + + byte[] v = ((RlpString) values.get(9)).getBytes(); + byte[] r = ((RlpString) values.get(10)).getBytes(); + byte[] s = ((RlpString) values.get(11)).getBytes(); + SignatureData signatureData = new SignatureData(v, r, s); + + ethereumAccessList.appendSignatures(signatureData); + return ethereumAccessList; + } catch (Exception e) { + throw new RuntimeException("There is an error while decoding process."); + } + } + + /** + * Appends signatures array to transaction. + * EthereumDynamicFee transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData SignatureData instance contains ECDSA signature data + */ + @Override + public void appendSignatures(SignatureData signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures().get(0))) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + } + + if (!Utils.isEmptySig(signatureData)) { + int v = Integer.decode(signatureData.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + } + super.appendSignatures(signatureData); + } + + /** + * Appends signatures array to transaction. + * EthereumDynamicFee transaction cannot have more than one signature, so an error occurs if the transaction already has a signature. + * @param signatureData List of SignatureData contains ECDSA signature data + */ + @Override + public void appendSignatures(List signatureData) { + if(this.getSignatures().size() != 0 && !Utils.isEmptySig(this.getSignatures())) { + throw new RuntimeException("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + } + + if(signatureData.size() != 1) { + throw new RuntimeException("Signatures are too long " + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + } + + SignatureData signature = signatureData.get(0); + if (!Utils.isEmptySig(signature)) { + int v = Integer.decode(signature.getV()); + if (v != 0 && v != 1) { + throw new RuntimeException("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + } + }; + + super.appendSignatures(signatureData); + } + + /** + * Returns a hash string of transaction + * @return String + */ + @Override + public String getTransactionHash() { + // TxHashRLP = 0x02 + encode([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, signatureYParity, signatureR, signatureS]) + String rlpEncoded = this.getRLPEncoding(); + byte[] rlpEncodedBytes = Numeric.hexStringToByteArray(rlpEncoded); + byte[] detachedType = Arrays.copyOfRange(rlpEncodedBytes, 1, rlpEncodedBytes.length); + return Hash.sha3(Numeric.toHexString(detachedType)); + } + + /** + * Signs to the transaction with a single private key. + * It sets Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyString The private key string. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(String keyString) throws IOException { + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * @param keyring The Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, Function signer) throws IOException { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + if(this.getFrom().equals("0x") || this.getFrom().equals(Utils.DEFAULT_ZERO_ADDRESS) || this.getFrom().isEmpty()){ + this.setFrom(keyring.getAddress()); + } + + if(!this.getFrom().toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + List sigList = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + + this.appendSignatures(sigList); + + return this; + } + + /** + * Signs using all private keys used in the role defined in the Keyring instance. + * It sets index and Hasher default value. + * - signer : TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring) throws IOException { + return this.sign(keyring, TransactionHasher::getHashForSignature); + } + + /** + * Signs to the transaction with a single private key. + * @param keyString The private key string + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(String keyString, Function signer) throws IOException { + + AbstractKeyring keyring = KeyringFactory.createFromPrivateKey(keyString); + return this.sign(keyring, signer); + } + + /** + * Signs to the transaction with a private key in the Keyring instance. + * It sets signer to TransactionHasher.getHashForSignature() + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index) throws IOException { + return this.sign(keyring, index, TransactionHasher::getHashForSignature); + } + + /** + * Signs to the transaction with a private key in the Keyring instance. + * @param keyring The Keyring instance. + * @param index The index of private key to use in Keyring instance. + * @param signer The function to get hash of transaction. + * @return AbstractTransaction + * @throws IOException + */ + @Override + public AbstractTransaction sign(AbstractKeyring keyring, int index, Function signer) throws IOException { + if(TransactionHelper.isEthereumTransaction(this.getType()) && keyring.isDecoupled()) { + throw new IllegalArgumentException(this.getType() + " cannot be signed with a decoupled keyring."); + } + + if(this.getFrom().equals("0x") || this.getFrom().equals(Utils.DEFAULT_ZERO_ADDRESS) || this.getFrom().isEmpty()){ + this.setFrom(keyring.getAddress()); + } + + if(!this.getFrom().toLowerCase().equals(keyring.getAddress().toLowerCase())) { + throw new IllegalArgumentException("The from address of the transaction is different with the address of the keyring to use"); + } + + this.fillTransaction(); + + String hash = signer.apply(this); + SignatureData signatureData = keyring.ecsign(hash, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex(), index); + + this.appendSignatures(signatureData); + + return this; + } + + /** + * Getter function for to + * + * @return String + */ + public String getTo() { + return to; + } + + /** + * Setter function for to + * + * @param to The account address that will receive the transferred value. + */ + public void setTo(String to) { + // "to" field in EthereumDynamicFee allows null + if (to == null || to.isEmpty()) { + to = "0x"; + } + + if (!to.equals("0x") && !Utils.isAddress(to)) { + throw new IllegalArgumentException("Invalid address. : " + to); + } + this.to = to; + } + + /** + * Getter function for input + * + * @return String + */ + public String getInput() { + return input; + } + + /** + * Setter function for input + * + * @param input Data attached to the transaction. + */ + public void setInput(String input) { + if (input == null) { + throw new IllegalArgumentException("input is missing"); + } + + if (!Utils.isHex(input)) { + throw new IllegalArgumentException("Invalid input : " + input); + } + this.input = Numeric.prependHexPrefix(input); + } + + + /** + * Getter function for value + * + * @return String + */ + public String getValue() { + return value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(String value) { + if (value == null) { + throw new IllegalArgumentException("value is missing"); + } + + if (!Utils.isNumber(value)) { + throw new IllegalArgumentException("Invalid value : " + value); + } + this.value = value; + } + + /** + * Setter function for value + * + * @param value The amount of KLAY in peb to be transferred. + */ + public void setValue(BigInteger value) { + setValue(Numeric.toHexStringWithPrefix(value)); + } + + /** + * Getter function for accessList + * + * @return accessList Access list is an EIP-2930 access list. + */ + public AccessList getAccessList() { + return accessList; + } + + /** + * Setter function for accessList + * + * @param accessList Access list is an EIP-2930 access list. + */ + public void setAccessList(AccessList accessList) { + if (accessList == null) { + accessList = new AccessList(); + } + this.accessList = accessList; + } + + /** + * Getter function for maxPriorityFeePerGas + * @return String + */ + public String getMaxPriorityFeePerGas() { + return maxPriorityFeePerGas; + } + + /** + * Setter function for maxPriorityFeePerGas. + * @param maxPriorityFeePerGas Max priority fee per gas. + */ + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + if(maxPriorityFeePerGas == null || maxPriorityFeePerGas.isEmpty() || maxPriorityFeePerGas.equals("0x")) { + maxPriorityFeePerGas = "0x"; + } + + if(!maxPriorityFeePerGas.equals("0x") && !Utils.isNumber(maxPriorityFeePerGas)) { + throw new IllegalArgumentException("Invalid maxPriorityFeePerGas. : " + maxPriorityFeePerGas); + } + + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + /** + * Setter function for maxPriorityFeePerGas. + * @param maxPriorityFeePerGas Max priority fee per gas. + */ + public void setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) { + setMaxPriorityFeePerGas(Numeric.toHexStringWithPrefix(maxPriorityFeePerGas)); + } + + /** + * Getter function for maxFeePerGas. + * @return String + */ + public String getMaxFeePerGas() { + return maxFeePerGas; + } + + /** + * Setter function for maxFeePerGas. + * @param maxFeePerGas Max fee per gas. + */ + public void setMaxFeePerGas(String maxFeePerGas) { + if(maxFeePerGas == null || maxFeePerGas.isEmpty() || maxFeePerGas.equals("0x")) { + maxFeePerGas = "0x"; + } + + if(!maxFeePerGas.equals("0x") && !Utils.isNumber(maxFeePerGas)) { + throw new IllegalArgumentException("Invalid maxFeePerGas. : " + maxFeePerGas); + } + + this.maxFeePerGas = maxFeePerGas; + } + + /** + * Setter function for gas price. + * @param maxFeePerGas Max fee per gas. + */ + public void setMaxFeePerGas(BigInteger maxFeePerGas) { + setMaxFeePerGas(Numeric.toHexStringWithPrefix(maxFeePerGas)); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java index 55d9e6528..3bd7687d3 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdate.java @@ -16,15 +16,20 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,6 +39,7 @@ * Represents a fee delegated account update transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedaccountupdate to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedAccountUpdate extends AbstractFeeDelegatedTransaction { /** @@ -41,11 +47,17 @@ public class FeeDelegatedAccountUpdate extends AbstractFeeDelegatedTransaction { */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedAccountUpdate Builder class. */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedAccountUpdate.toString()); @@ -56,6 +68,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedAccountUpdate build() { return new FeeDelegatedAccountUpdate(this); } @@ -95,6 +117,7 @@ public static FeeDelegatedAccountUpdate create(Klay klaytnCall, String from, Str public FeeDelegatedAccountUpdate(FeeDelegatedAccountUpdate.Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -117,13 +140,45 @@ public FeeDelegatedAccountUpdate(Klay klaytnCall, String from, String nonce, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -293,10 +348,79 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedAccountUpdate.getGasPrice())) != 0) return false; return true; } + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedAccountUpdate txObj = (FeeDelegatedAccountUpdate) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java index 8a76e103f..5e21a1c5e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedAccountUpdateWithRatio.java @@ -16,15 +16,20 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,6 +39,7 @@ * Represents a fee delegated account update with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedaccountupdatewithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedAccountUpdateWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** @@ -41,11 +47,17 @@ public class FeeDelegatedAccountUpdateWithRatio extends AbstractFeeDelegatedWith */ Account account; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedAccountUpdateWithRatio Builder class. */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { Account account; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedAccountUpdateWithRatio.toString()); @@ -56,6 +68,16 @@ public Builder setAccount(Account account) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedAccountUpdateWithRatio build() { return new FeeDelegatedAccountUpdateWithRatio(this); } @@ -96,6 +118,7 @@ public static FeeDelegatedAccountUpdateWithRatio create(Klay klaytnCall, String public FeeDelegatedAccountUpdateWithRatio(Builder builder) { super(builder); setAccount(builder.account); + setGasPrice(builder.gasPrice); } /** @@ -119,7 +142,6 @@ public FeeDelegatedAccountUpdateWithRatio(Klay klaytnCall, String from, String n from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -127,8 +149,42 @@ public FeeDelegatedAccountUpdateWithRatio(Klay klaytnCall, String from, String n feeRatio ); setAccount(account); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded FeeDelegatedAccountUpdateWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedAccountUpdateWithRatio string. @@ -300,10 +356,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getAccount().getRLPEncodingAccountKey().equals(feeDelegatedAccountUpdate.getAccount().getRLPEncodingAccountKey())) { return false; } + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedAccountUpdate.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedAccountUpdateWithRatio txObj = (FeeDelegatedAccountUpdateWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for Account * @return Account diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java index acec35cb8..9ad883fda 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancel.java @@ -16,15 +16,19 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.account.Account; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,16 +38,33 @@ * Represents a fee delegated cancel transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedcancel to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedCancel extends AbstractFeeDelegatedTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * FeeDelegatedCancel Builder class. */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeFeeDelegatedCancel.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedCancel build() { return new FeeDelegatedCancel(this); } @@ -81,6 +102,7 @@ public static FeeDelegatedCancel create(Klay klaytnCall, String from, String non */ public FeeDelegatedCancel(FeeDelegatedCancel.Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -102,12 +124,44 @@ public FeeDelegatedCancel(Klay klaytnCall, String from, String nonce, String gas from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -261,7 +315,85 @@ public String getSenderTxHash() { public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean checkSig) { if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancel)) return false; + FeeDelegatedCancel feeDelegatedCancel = (FeeDelegatedCancel) txObj; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedCancel.getGasPrice())) != 0) return false; return true; } + + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedCancel txObj = (FeeDelegatedCancel) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java index c85c1bbf7..3dd0724b0 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedCancelWithRatio.java @@ -16,14 +16,19 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; +import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.crypto.Hash; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,16 +38,33 @@ * Represents a fee delegated cancel with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedcancelwithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedCancelWithRatio extends AbstractFeeDelegatedWithRatioTransaction { + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; /** * FeeDelegatedCancelWithRatio Builder class. */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { + String gasPrice = "0x"; + public Builder() { super(TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString()); } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedCancelWithRatio build() { return new FeeDelegatedCancelWithRatio(this); } @@ -81,6 +103,7 @@ public static FeeDelegatedCancelWithRatio create(Klay klaytnCall, String from, S */ public FeeDelegatedCancelWithRatio(Builder builder) { super(builder); + setGasPrice(builder.gasPrice); } /** @@ -97,7 +120,51 @@ public FeeDelegatedCancelWithRatio(Builder builder) { * @param feeRatio A fee ratio of the fee payer. */ public FeeDelegatedCancelWithRatio(Klay klaytnCall, String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String feePayer, List feePayerSignatures, String feeRatio) { - super(klaytnCall, TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString(), from, nonce, gas, gasPrice, chainId, signatures, feePayer, feePayerSignatures, feeRatio); + super( + klaytnCall, + TransactionType.TxTypeFeeDelegatedCancelWithRatio.toString(), + from, + nonce, + gas, + chainId, + signatures, + feePayer, + feePayerSignatures, + feeRatio + ); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -257,7 +324,78 @@ public String getSenderTxHash() { public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, boolean checkSig) { if(!super.compareTxField(txObj, checkSig)) return false; if(!(txObj instanceof FeeDelegatedCancelWithRatio)) return false; + FeeDelegatedCancelWithRatio feeDelegatedCancelWithRatio = (FeeDelegatedCancelWithRatio) txObj; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedCancelWithRatio.getGasPrice())) != 0) return false; return true; } + + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedCancelWithRatio txObj = (FeeDelegatedCancelWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java index ee7436d53..7521e2cab 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoring.java @@ -16,9 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -36,17 +39,24 @@ * Represents a fee delegated chain data anchoring transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedchaindataanchoring to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedChainDataAnchoring extends AbstractFeeDelegatedTransaction { /** * Data of the service chain. */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedChainDataAnchoring Builder class */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedChainDataAnchoring.toString()); @@ -57,6 +67,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedChainDataAnchoring build() { return new FeeDelegatedChainDataAnchoring(this); } @@ -96,6 +116,7 @@ public static FeeDelegatedChainDataAnchoring create(Klay klaytnCall, String from public FeeDelegatedChainDataAnchoring(Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -118,13 +139,45 @@ public FeeDelegatedChainDataAnchoring(Klay klaytnCall, String from, String nonce from, nonce, gas, - gasPrice, chainId, signatures, feePayer, feePayerSignatures ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -286,10 +339,86 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che FeeDelegatedChainDataAnchoring feeDelegatedChainDataAnchoring = (FeeDelegatedChainDataAnchoring)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoring.getInput())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedChainDataAnchoring.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedChainDataAnchoring txObj = (FeeDelegatedChainDataAnchoring) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java index a5dba7c2e..926a520fa 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedChainDataAnchoringWithRatio.java @@ -16,8 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,17 +38,24 @@ * Represents a fee delegated chain data anchoring with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedchaindataanchoringwithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedChainDataAnchoringWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** * Data of the service chain. */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedChainDataAnchoringWithRatio Builder class */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedChainDataAnchoringWithRatio.toString()); @@ -55,6 +66,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedChainDataAnchoringWithRatio build() { return new FeeDelegatedChainDataAnchoringWithRatio(this); } @@ -95,6 +116,7 @@ public static FeeDelegatedChainDataAnchoringWithRatio create(Klay klaytnCall, St public FeeDelegatedChainDataAnchoringWithRatio(Builder builder) { super(builder); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -118,7 +140,6 @@ public FeeDelegatedChainDataAnchoringWithRatio(Klay klaytnCall, String from, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -126,6 +147,39 @@ public FeeDelegatedChainDataAnchoringWithRatio(Klay klaytnCall, String from, Str feeRatio ); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -290,10 +344,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo FeeDelegatedChainDataAnchoringWithRatio feeDelegatedChainDataAnchoringWithRatio = (FeeDelegatedChainDataAnchoringWithRatio)txObj; if(!this.getInput().equals(feeDelegatedChainDataAnchoringWithRatio.getInput())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedChainDataAnchoringWithRatio.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedChainDataAnchoringWithRatio txObj = (FeeDelegatedChainDataAnchoringWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for input * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java index f7d073991..e43a8572a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeploy.java @@ -16,8 +16,10 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,8 +38,8 @@ * Represents a fee delegated smart contract deploy transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractdeploy to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransaction { - /** * The account address that will receive the transferred value. */ @@ -64,6 +67,11 @@ public class FeeDelegatedSmartContractDeploy extends AbstractFeeDelegatedTransac */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractDeploy Builder class */ @@ -73,6 +81,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedSmartContractDeploy txObj = (FeeDelegatedSmartContractDeploy) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java index ad7ff4d53..1f0dd9ace 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractDeployWithRatio.java @@ -16,8 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -26,6 +29,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +39,7 @@ * Represents a fee delegated smart contract deploy with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedsmartcontractdeploywithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractDeployWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** @@ -64,6 +69,11 @@ public class FeeDelegatedSmartContractDeployWithRatio extends AbstractFeeDelegat */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractDeployWithRatio Builder class */ @@ -73,6 +83,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String input; boolean humanReadable = false; String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedSmartContractDeployWithRatio.toString()); @@ -113,6 +124,16 @@ public Builder setCodeFormat(BigInteger codeFormat) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedSmartContractDeployWithRatio build() { return new FeeDelegatedSmartContractDeployWithRatio(this); } @@ -161,6 +182,7 @@ public FeeDelegatedSmartContractDeployWithRatio(Builder builder) { setInput(builder.input); setHumanReadable(builder.humanReadable); setCodeFormat(builder.codeFormat); + setGasPrice(builder.gasPrice); } /** @@ -188,7 +210,6 @@ public FeeDelegatedSmartContractDeployWithRatio(Klay klaytnCall, String from, St from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -200,8 +221,42 @@ public FeeDelegatedSmartContractDeployWithRatio(Klay klaytnCall, String from, St setInput(input); setHumanReadable(humanReadable); setCodeFormat(codeFormat); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; } + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + } + + /** * Decodes a RLP-encoded FeeDelegatedSmartContractDeployWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedSmartContractDeployWithRatio string. @@ -391,10 +446,86 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getInput().equals(feeDelegatedSmartContractDeployWithRatio.getInput())) return false; if(this.getHumanReadable() != feeDelegatedSmartContractDeployWithRatio.getHumanReadable()) return false; if(!this.getCodeFormat().equals(feeDelegatedSmartContractDeployWithRatio.getCodeFormat())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedSmartContractDeployWithRatio.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedSmartContractDeployWithRatio txObj = (FeeDelegatedSmartContractDeployWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java index 594be3894..71fdd7332 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecution.java @@ -16,9 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +38,7 @@ * Represents a fee delegated smart contract execution transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedsmartcontractexecution to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractExecution extends AbstractFeeDelegatedTransaction { /** * The account address that will receive the transferred value. @@ -51,6 +55,11 @@ public class FeeDelegatedSmartContractExecution extends AbstractFeeDelegatedTran */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractExecution Builder class */ @@ -58,6 +67,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedSmartContractExecution txObj = (FeeDelegatedSmartContractExecution) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java index 43e8f3a78..1a3d8cc6e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedSmartContractExecutionWithRatio.java @@ -16,8 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,11 +28,13 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedSmartContractExecutionWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** * The account address that will receive the transferred value. @@ -46,6 +51,11 @@ public class FeeDelegatedSmartContractExecutionWithRatio extends AbstractFeeDele */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedSmartContractExecutionWithRatio Builder class */ @@ -53,6 +63,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String to; String value = "0x00"; String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedSmartContractExecutionWithRatio.toString()); @@ -78,6 +89,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedSmartContractExecutionWithRatio build() { return new FeeDelegatedSmartContractExecutionWithRatio(this); } @@ -122,6 +143,7 @@ public FeeDelegatedSmartContractExecutionWithRatio(Builder builder) { setTo(builder.to); setValue(builder.value); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -147,7 +169,6 @@ public FeeDelegatedSmartContractExecutionWithRatio(Klay klaytnCall, String from, from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -157,8 +178,42 @@ public FeeDelegatedSmartContractExecutionWithRatio(Klay klaytnCall, String from, setTo(to); setValue(value); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } + /** * Decodes a RLP-encoded FeeDelegatedSmartContractExecutionWithRatio string. * @param rlpEncoded RLP-encoded FeeDelegatedSmartContractExecutionWithRatio string. @@ -335,10 +390,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedSmartContractExecutionWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedSmartContractExecutionWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedSmartContractExecutionWithRatio.getInput())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedSmartContractExecutionWithRatio.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedSmartContractExecutionWithRatio txObj = (FeeDelegatedSmartContractExecutionWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java index fd3b40a1b..854b8d7fd 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransfer.java @@ -16,9 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.crypto.KlaySignatureData; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +38,7 @@ * Represents a fee delegated value transfer transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfer to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransfer extends AbstractFeeDelegatedTransaction { /** @@ -47,12 +51,18 @@ public class FeeDelegatedValueTransfer extends AbstractFeeDelegatedTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransfer Builder class */ public static class Builder extends AbstractFeeDelegatedTransaction.Builder { String to; String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransfer.toString()); @@ -73,6 +83,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransfer build() { return new FeeDelegatedValueTransfer(this); } @@ -114,6 +134,7 @@ public FeeDelegatedValueTransfer(Builder builder) { super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -137,7 +158,6 @@ public FeeDelegatedValueTransfer(Klay klaytnCall, String from, String nonce, Str from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -145,6 +165,39 @@ public FeeDelegatedValueTransfer(Klay klaytnCall, String from, String nonce, Str ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -315,10 +368,86 @@ public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean che if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransfer.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransfer.getValue()))) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedValueTransfer.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedValueTransfer txObj = (FeeDelegatedValueTransfer) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Setter function for to * @param to The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java index 445e354fa..5b6bee719 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemo.java @@ -16,9 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -26,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,8 +38,8 @@ * Represents a fee delegated value transfer memo transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/fee-delegation#txtypefeedelegatedvaluetransfermemo to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransaction { - /** * The account address that will receive the transferred value. */ @@ -52,6 +55,11 @@ public class FeeDelegatedValueTransferMemo extends AbstractFeeDelegatedTransacti */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferMemo Builder class */ @@ -59,6 +67,7 @@ public static class Builder extends AbstractFeeDelegatedTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedValueTransferMemo txObj = (FeeDelegatedValueTransferMemo) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java index fa2388e52..df4ff670e 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferMemoWithRatio.java @@ -16,8 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,6 +38,7 @@ * Represents a fee delegated value transfer memo with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransfermemowithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransferMemoWithRatio extends AbstractFeeDelegatedWithRatioTransaction { /** @@ -51,6 +56,11 @@ public class FeeDelegatedValueTransferMemoWithRatio extends AbstractFeeDelegated */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferMemo Builder class */ @@ -58,6 +68,7 @@ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Bui String to; String value; String input; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransferMemoWithRatio.toString()); @@ -82,6 +93,16 @@ public Builder setInput(String input) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransferMemoWithRatio build() { return new FeeDelegatedValueTransferMemoWithRatio(this); } @@ -126,6 +147,7 @@ public FeeDelegatedValueTransferMemoWithRatio(FeeDelegatedValueTransferMemoWithR setTo(builder.to); setValue(builder.value); setInput(builder.input); + setGasPrice(builder.gasPrice); } /** @@ -151,7 +173,6 @@ public FeeDelegatedValueTransferMemoWithRatio(Klay klaytnCall, String from, Stri from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -161,6 +182,39 @@ public FeeDelegatedValueTransferMemoWithRatio(Klay klaytnCall, String from, Stri setTo(to); setValue(value); setInput(input); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -341,10 +395,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferMemoWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferMemoWithRatio.getValue()))) return false; if(!this.getInput().equals(feeDelegatedValueTransferMemoWithRatio.getInput())) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedValueTransferMemoWithRatio.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedValueTransferMemoWithRatio txObj = (FeeDelegatedValueTransferMemoWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java index e3b646712..67e336cf1 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/FeeDelegatedValueTransferWithRatio.java @@ -16,8 +16,11 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction; import com.klaytn.caver.transaction.AbstractFeeDelegatedWithRatioTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; @@ -25,6 +28,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -35,8 +39,8 @@ * Represents a fee delegated value transfer with ratio transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/partial-fee-delegation#txtypefeedelegatedvaluetransferwithratio to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWithRatioTransaction { - /** * The account address that will receive the transferred value. */ @@ -47,12 +51,18 @@ public class FeeDelegatedValueTransferWithRatio extends AbstractFeeDelegatedWith */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * FeeDelegatedValueTransferWithRatio Builder class */ public static class Builder extends AbstractFeeDelegatedWithRatioTransaction.Builder { String to; String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeFeeDelegatedValueTransferWithRatio.toString()); @@ -73,6 +83,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public FeeDelegatedValueTransferWithRatio build() { return new FeeDelegatedValueTransferWithRatio(this); } @@ -115,6 +135,7 @@ public FeeDelegatedValueTransferWithRatio(FeeDelegatedValueTransferWithRatio.Bui super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -139,7 +160,6 @@ public FeeDelegatedValueTransferWithRatio(Klay klaytnCall, String from, String n from, nonce, gas, - gasPrice, chainId, signatures, feePayer, @@ -148,6 +168,39 @@ public FeeDelegatedValueTransferWithRatio(Klay klaytnCall, String from, String n ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -322,10 +375,85 @@ public boolean compareTxField(AbstractFeeDelegatedWithRatioTransaction txObj, bo if(!this.getTo().toLowerCase().equals(feeDelegatedValueTransferWithRatio.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(feeDelegatedValueTransferWithRatio.getValue()))) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(feeDelegatedValueTransferWithRatio.getGasPrice())) != 0) return false; return true; } + /** + * Combines signatures to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined. + * When combining the signatures into a transaction instance, + * an error is thrown if the decoded transaction contains different value except signatures. + * @param rlpEncoded A List of RLP-encoded transaction strings. + * @return String + */ + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + // At initial state of AbstractFeeDelegateTx Object, feePayerSignature field has one empty signature. + if((Utils.isEmptySig(this.getFeePayerSignatures())) || Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractFeeDelegatedTransaction decode = (AbstractFeeDelegatedTransaction) TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + FeeDelegatedValueTransferWithRatio txObj = (FeeDelegatedValueTransferWithRatio) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + if(!txObj.getFeePayer().equals("0x") && !txObj.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) { + this.setFeePayer(txObj.getFeePayer()); + fillVariable = false; + } + } + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + this.appendFeePayerSignatures(txObj.getFeePayerSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + /** * Setter function for to * @param to The account address that will receive the transferred value. diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java index 025c8a641..66df650a5 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/LegacyTransaction.java @@ -16,17 +16,21 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +@JsonIgnoreProperties(value = { "chainId" }) public class LegacyTransaction extends AbstractTransaction { /** * The account address that will receive the transferred value. @@ -43,6 +47,11 @@ public class LegacyTransaction extends AbstractTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * LegacyTransaction Builder class */ @@ -50,6 +59,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + LegacyTransaction txObj = (LegacyTransaction) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java index 925619024..3197fb38c 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractDeploy.java @@ -16,8 +16,10 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.CodeFormat; import com.klaytn.caver.utils.Utils; @@ -25,6 +27,7 @@ import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -34,6 +37,7 @@ * Represents a smart contract deploy transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypesmartcontractdeploy to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class SmartContractDeploy extends AbstractTransaction { /** @@ -63,6 +67,11 @@ public class SmartContractDeploy extends AbstractTransaction { */ String codeFormat = Numeric.toHexStringWithPrefix(CodeFormat.EVM); + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * SmartContractDeploy Builder class */ @@ -72,6 +81,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + SmartContractDeploy txObj = (SmartContractDeploy) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to. * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java index 994f3e351..a2e14c83f 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/SmartContractExecution.java @@ -16,14 +16,17 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,6 +36,7 @@ * Represents a smart contract execution transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypesmartcontractexecution to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class SmartContractExecution extends AbstractTransaction { /** @@ -50,6 +54,11 @@ public class SmartContractExecution extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * SmartContractExecution Builder class */ @@ -57,6 +66,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + SmartContractExecution txObj = (SmartContractExecution) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java index 0ec21ae9a..d213e4e5a 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/TransactionType.java @@ -16,6 +16,8 @@ package com.klaytn.caver.transaction.type; +import java.util.Objects; + public enum TransactionType { TxTypeLegacyTransaction(0x00), @@ -45,10 +47,13 @@ public enum TransactionType { TxTypeChainDataAnchoring(0x48), TxTypeFeeDelegatedChainDataAnchoring(0x49), - TxTypeFeeDelegatedChainDataAnchoringWithRatio(0x4a); + TxTypeFeeDelegatedChainDataAnchoringWithRatio(0x4a), + + TxTypeEthereumAccessList(0x7801), + TxTypeEthereumDynamicFee(0x7802); int type; - + TransactionType(int type) { this.type = type; } @@ -56,4 +61,5 @@ public enum TransactionType { public int getType() { return type; } + } diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java index c4e1c4af0..f7d86ce07 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransfer.java @@ -16,14 +16,17 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,6 +36,7 @@ * Represents a value transfer transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypevaluetransfer to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class ValueTransfer extends AbstractTransaction { /** * The account address that will receive the transferred value. @@ -44,12 +48,18 @@ public class ValueTransfer extends AbstractTransaction { */ String value; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ValueTransfer Builder class */ public static class Builder extends AbstractTransaction.Builder { private String to; private String value; + String gasPrice = "0x"; public Builder() { super(TransactionType.TxTypeValueTransfer.toString()); @@ -71,6 +81,16 @@ public Builder setValue(BigInteger value) { return this; } + public Builder setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public Builder setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); + return this; + } + public ValueTransfer build() { return new ValueTransfer(this); } @@ -110,6 +130,7 @@ public ValueTransfer(ValueTransfer.Builder builder) { super(builder); setTo(builder.to); setValue(builder.value); + setGasPrice(builder.gasPrice); } /** @@ -131,12 +152,44 @@ public ValueTransfer(Klay klaytnCall, String from, String nonce, String gas, Str from, nonce, gas, - gasPrice, chainId, signatures ); setTo(to); setValue(value); + setGasPrice(gasPrice); + } + + /** + * Getter function for gas price + * @return String + */ + public String getGasPrice() { + return gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(String gasPrice) { + if(gasPrice == null || gasPrice.isEmpty() || gasPrice.equals("0x")) { + gasPrice = "0x"; + } + + if(!gasPrice.equals("0x") && !Utils.isNumber(gasPrice)) { + throw new IllegalArgumentException("Invalid gasPrice. : " + gasPrice); + } + + this.gasPrice = gasPrice; + } + + /** + * Setter function for gas price. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + */ + public void setGasPrice(BigInteger gasPrice) { + setGasPrice(Numeric.toHexStringWithPrefix(gasPrice)); } /** @@ -260,10 +313,72 @@ public boolean compareTxField(AbstractTransaction obj, boolean checkSig) { if(!this.getTo().toLowerCase().equals(txObj.getTo().toLowerCase())) return false; if(!Numeric.toBigInt(this.getValue()).equals(Numeric.toBigInt(txObj.getValue()))) return false; + if(Numeric.toBigInt(this.getGasPrice()).compareTo(Numeric.toBigInt(txObj.getGasPrice())) != 0) return false; return true; } + @Override + public String combineSignedRawTransactions(List rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + ValueTransfer txObj = (ValueTransfer) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java index f758b4000..ce6ac8e4d 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java +++ b/core/src/main/java/com/klaytn/caver/transaction/type/ValueTransferMemo.java @@ -16,14 +16,17 @@ package com.klaytn.caver.transaction.type; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.klaytn.caver.rpc.Klay; import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.SignatureData; import org.web3j.rlp.*; import org.web3j.utils.Numeric; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -33,6 +36,7 @@ * Represents a value transfer memo transaction. * Please refer to https://docs.klaytn.com/klaytn/design/transactions/basic#txtypevaluetransfermemo to see more detail. */ +@JsonIgnoreProperties(value = { "chainId" }) public class ValueTransferMemo extends AbstractTransaction { /** @@ -50,6 +54,11 @@ public class ValueTransferMemo extends AbstractTransaction { */ String input; + /** + * A unit price of gas in peb the sender will pay for a transaction fee. + */ + String gasPrice = "0x"; + /** * ValueTransferMemo Builder class */ @@ -57,6 +66,7 @@ public static class Builder extends AbstractTransaction.Builder rlpEncoded) { + boolean fillVariable = false; + + // If the signatures are empty, there may be an undefined member variable. + // In this case, the empty information is filled with the decoded result. + if(Utils.isEmptySig(this.getSignatures())) fillVariable = true; + + for(String encodedStr : rlpEncoded) { + AbstractTransaction decode = TransactionDecoder.decode(encodedStr); + if (!decode.getType().equals(this.getType())) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + ValueTransferMemo txObj = (ValueTransferMemo) decode; + + if(fillVariable) { + if(this.getNonce().equals("0x")) this.setNonce(txObj.getNonce()); + if(this.getGasPrice().equals("0x")) this.setGasPrice(txObj.getGasPrice()); + fillVariable = false; + } + + // Signatures can only be combined for the same transaction. + // Therefore, compare whether the decoded transaction is the same as this. + if(!this.compareTxField(txObj, false)) { + throw new RuntimeException("Transactions containing different information cannot be combined."); + } + + this.appendSignatures(txObj.getSignatures()); + } + + return this.getRLPEncoding(); + } + + /** + * Fills empty optional transaction field.(gasPrice) + * @throws IOException + */ + @Override + public void fillTransaction() throws IOException { + super.fillTransaction(); + if(this.gasPrice.equals("0x")) { + this.setGasPrice(this.getKlaytnCall().getGasPrice().send().getResult()); + } + if(this.getGasPrice().equals("0x")) { + throw new RuntimeException("Cannot fill transaction data. (gasPrice). `klaytnCall` must be set in Transaction instance to automatically fill the nonce, chainId or gasPrice. Please call the `setKlaytnCall` to set `klaytnCall` in the Transaction instance."); + } + } + + /** + * Checks that member variables that can be defined by the user are defined. + * If there is an undefined variable, an error occurs. + */ + @Override + public void validateOptionalValues(boolean checkChainID) { + super.validateOptionalValues(checkChainID); + if(this.getGasPrice() == null || this.getGasPrice().isEmpty() || this.getGasPrice().equals("0x")) { + throw new RuntimeException("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + } + } + + /** * Getter function for to * @return String diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java new file mode 100644 index 000000000..2341b3bfa --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumAccessListWrapper.java @@ -0,0 +1,112 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.type.wrapper; + +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.wallet.keyring.SignatureData; + +import java.util.List; + +/** + * Represents a EthereumAccessListWrapper + * 1. This class wraps all of static methods of EthereumAccessList + * 2. This class should be accessed via `caver.transaction.ethereumAccessList` + */ +public class EthereumAccessListWrapper { + /** + * Klay RPC instance + */ + private Klay klaytnCall; + + /** + * Creates a EthereumAccessListWrapper instance. + * @param klaytnCall Klay RPC instance + */ + public EthereumAccessListWrapper(Klay klaytnCall) { + this.klaytnCall = klaytnCall; + } + + /** + * Creates a EthereumAccessList instance derived from a RLP-encoded EthereumAccessList string. + * @param rlpEncoded RLP-encoded EthereumAccessList string + * @return EthereumAccessList + */ + public EthereumAccessList create(String rlpEncoded) { + EthereumAccessList ethereumAccessList = EthereumAccessList.decode(rlpEncoded); + ethereumAccessList.setKlaytnCall(this.klaytnCall); + return ethereumAccessList; + } + + /** + * Creates a EthereumAccessList instance derived from a RLP-encoded EthereumAccessList byte array. + * @param rlpEncoded RLP-encoded EthereumAccessList byte array. + * @return EthereumAccessList + */ + public EthereumAccessList create(byte[] rlpEncoded) { + EthereumAccessList ethereumAccessList = EthereumAccessList.decode(rlpEncoded); + ethereumAccessList.setKlaytnCall(this.klaytnCall); + return ethereumAccessList; + } + + /** + * Creates a EthereumAccessList instance using EthereumAccessList.Builder + * @param builder EthereumAccessList.Builder + * @return EthereumAccessList + */ + public EthereumAccessList create(EthereumAccessList.Builder builder) { + builder.setKlaytnCall(this.klaytnCall); + return EthereumAccessList.create(builder); + } + + /** + * Create a EthereumAccessList instance. + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param gasPrice A unit price of gas in peb the sender will pay for a transaction fee. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumAccessList + */ + public EthereumAccessList create(String from, String nonce, String gas, String gasPrice, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return EthereumAccessList.create(this.klaytnCall, from, nonce, gas, gasPrice, chainId, signatures, to, input, value, accessList); + } + + /** + * Decodes a RLP-encoded EthereumAccessList string. + * @param rlpEncoded RLP-encoded EthereumAccessList string + * @return EthereumAccessList + */ + public EthereumAccessList decode(String rlpEncoded) { + return EthereumAccessList.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded EthereumAccessList byte array. + * @param rlpEncoded RLP-encoded EthereumAccessList byte array. + * @return EthereumAccessList + */ + public EthereumAccessList decode(byte[] rlpEncoded) { + return EthereumAccessList.decode(rlpEncoded); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java new file mode 100644 index 000000000..083f281e3 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/type/wrapper/EthereumDynamicFeeWrapper.java @@ -0,0 +1,113 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.type.wrapper; + +import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.type.EthereumDynamicFee; +import com.klaytn.caver.wallet.keyring.SignatureData; + +import java.util.List; + +/** + * Represents a EthereumDynamicFeeWrapper + * 1. This class wraps all of static methods of EthereumDynamicFee + * 2. This class should be accessed via `caver.transaction.ethereumDynamicFee` + */ +public class EthereumDynamicFeeWrapper { + /** + * Klay RPC instance + */ + private Klay klaytnCall; + + /** + * Creates a EthereumDynamicFeeWrapper instance. + * @param klaytnCall Klay RPC instance + */ + public EthereumDynamicFeeWrapper(Klay klaytnCall) { + this.klaytnCall = klaytnCall; + } + + /** + * Creates a EthereumDynamicFee instance derived from a RLP-encoded EthereumDynamicFee string. + * @param rlpEncoded RLP-encoded EthereumDynamicFee string + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(String rlpEncoded) { + EthereumDynamicFee ethereumDynamicFee = EthereumDynamicFee.decode(rlpEncoded); + ethereumDynamicFee.setKlaytnCall(this.klaytnCall); + return ethereumDynamicFee; + } + + /** + * Creates a EthereumDynamicFee instance derived from a RLP-encoded EthereumDynamicFee byte array. + * @param rlpEncoded RLP-encoded EthereumDynamicFee byte array. + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(byte[] rlpEncoded) { + EthereumDynamicFee ethereumDynamicFee = EthereumDynamicFee.decode(rlpEncoded); + ethereumDynamicFee.setKlaytnCall(this.klaytnCall); + return ethereumDynamicFee; + } + + /** + * Creates a EthereumDynamicFee instance using EthereumDynamicFee.Builder + * @param builder EthereumDynamicFee.Builder + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(EthereumDynamicFee.Builder builder) { + builder.setKlaytnCall(this.klaytnCall); + return EthereumDynamicFee.create(builder); + } + + /** + * Create a EthereumDynamicFee instance. + * @param from The address of the sender. + * @param nonce A value used to uniquely identify a sender’s transaction. + * @param gas The maximum amount of gas the transaction is allowed to use. + * @param maxPriorityFeePerGas Max priority fee per gas. + * @param maxFeePerGas Max fee per gas. + * @param chainId Network ID + * @param signatures A Signature list + * @param to The account address that will receive the transferred value. + * @param input Data attached to the transaction, used for transaction execution. + * @param value The amount of KLAY in peb to be transferred. + * @param accessList The EIP-2930 access list. + * @return EthereumDynamicFee + */ + public EthereumDynamicFee create(String from, String nonce, String gas, String maxPriorityFeePerGas, String maxFeePerGas, String chainId, List signatures, String to, String input, String value, AccessList accessList) { + return EthereumDynamicFee.create(this.klaytnCall, from, nonce, gas, maxPriorityFeePerGas, maxFeePerGas, chainId, signatures, to, input, value, accessList); + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee string. + * @param rlpEncoded RLP-encoded EthereumDynamicFee string + * @return EthereumDynamicFee + */ + public EthereumDynamicFee decode(String rlpEncoded) { + return EthereumDynamicFee.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded EthereumDynamicFee byte array. + * @param rlpEncoded RLP-encoded EthereumDynamicFee byte array. + * @return EthereumDynamicFee + */ + public EthereumDynamicFee decode(byte[] rlpEncoded) { + return EthereumDynamicFee.decode(rlpEncoded); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java new file mode 100644 index 000000000..1b972d02c --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessList.java @@ -0,0 +1,111 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.utils; + +import org.web3j.rlp.*; +import org.web3j.utils.Numeric; + +import java.util.*; + +/** + * Represents an access list. + * AccessList is an EIP-2930 access list. + */ +public class AccessList extends ArrayList { + /** + * Constructor. + */ + public AccessList() {} + + /** + * Creates an AccessList instance from given list of AccessTuple. + * @param items A list of AccessTuple. + */ + public AccessList(List items) { + this.addAll(items); + } + + /** + * Creates an Access list. + * + * @param items + * @return + */ + public static AccessList create(List items) { + AccessList accessList = new AccessList(items); + return accessList; + } + + /** + * Returns a decoded access list. + * + * @param accessListRlpList List of RlpType to decode. + * @return AccessList + */ + public static AccessList decode(RlpList accessListRlpList) { + List accessTupleRlps = accessListRlpList.getValues(); + AccessList accessList = new AccessList(); + for (int i =0 ; i < accessTupleRlps.size(); i++) { + accessList.add(AccessTuple.decode((RlpList) accessTupleRlps.get(i))); + } + return accessList; + } + + /** + * Returns a decoded access list. + * + * @param rlpEncoded Rlp encoded string of access list. + * @return + */ + public static AccessList decode(String rlpEncoded) { + return AccessList.decode(Numeric.hexStringToByteArray(rlpEncoded)); + } + + /** + * Returns a decoded access list. + * + * @param rlpEncoded Rlp encoded byte array of access list. + * @return + */ + public static AccessList decode(byte[] rlpEncoded) { + RlpList rlpList = RlpDecoder.decode(rlpEncoded); + return AccessList.decode(((RlpList) rlpList.getValues().get(0))); + } + + /** + * Returns the RLP-encoded string of this accessList. + * + * @return RlpList + */ + public RlpList toRlpList() { + List rlpTypeList = new ArrayList<>(); + for (AccessTuple accessTuple : this) { + rlpTypeList.add(accessTuple.toRlpList()); + } + return new RlpList(rlpTypeList); + } + + /** + * Returns an encoded access tuple. + * + * @return byte[] + */ + public byte[] encodeToBytes() { + RlpList rlPList = this.toRlpList(); + return RlpEncoder.encode(rlPList); + } +} diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java new file mode 100644 index 000000000..19d598440 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/AccessTuple.java @@ -0,0 +1,198 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.utils; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.klaytn.caver.utils.Utils; +import org.web3j.rlp.RlpEncoder; +import org.web3j.rlp.RlpList; +import org.web3j.rlp.RlpString; +import org.web3j.rlp.RlpType; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * AccessTuple is a class representing an element of EIP-2930 access list. + */ +@JsonDeserialize(using = AccessTuple.AccessTupleDeserializer.class) +public class AccessTuple { + private String address; + private List storageKeys; + + /** + * Create an AccessTuple instance. + * @param address An address string. + * @param storageKeys A list of storage keys. + */ + public AccessTuple(String address, List storageKeys) { + this.setAddress(address); + this.setStorageKeys(storageKeys); + } + + /** + * Create an AccessTuple instance. + * @param address + * @param storageKeys + */ + public static AccessTuple create(String address, List storageKeys) { + return new AccessTuple(address, storageKeys); + } + + /** + * Getter function of address. + * @return String + */ + public String getAddress() { + return address; + } + + /** + * Setter function of address. + * @param address + */ + public void setAddress(String address) { + if (!Utils.isAddress(address)) { + throw new IllegalArgumentException("Invalid address. Address: " + address); + } + this.address = Utils.addHexPrefix(address); + } + + /** + * Getter function of storageKeys. + * @return List<String> + */ + public List getStorageKeys() { + return storageKeys; + } + + /** + * Setter function of storageKeys. + * @param storageKeys A list of storage keys. + */ + public void setStorageKeys(List storageKeys) { + for (int i = 0; i < storageKeys.size(); i++) { + String storageKey = Utils.addHexPrefix(storageKeys.get(i).toLowerCase()); + if (!Utils.isHex(storageKey) || storageKey.length() != 66) { + throw new IllegalArgumentException("Invalid storageKey. Storage key should be a 32 bytes of hex string " + storageKey); + } + storageKeys.set(i, storageKey); + } + java.util.Collections.sort(storageKeys); + this.storageKeys = storageKeys; + } + + /** + * Decodes given RlpList to AccessTuple. + * @param rlpEncodedAccessTuple RlpList representing rlp encoded access tuple. + * @return + */ + public static AccessTuple decode(RlpList rlpEncodedAccessTuple) { + try { + List accessTupleRlp = rlpEncodedAccessTuple.getValues(); + String address = ((RlpString) accessTupleRlp.get(0)).asString(); + List storageKeys = new ArrayList<>(); + List storageKeysRlpType = ((RlpList) (accessTupleRlp.get(1))).getValues(); + for (RlpType storageKeyRlpType : storageKeysRlpType) { + storageKeys.add(((RlpString) storageKeyRlpType).asString()); } + return new AccessTuple(address, storageKeys); + } catch (Exception e) { + throw new RuntimeException("There is an error while decoding process."); + } + } + + + /** + * Returns the RLP-encoded string of this accessTuple. + * + * @return RlpList + */ + public RlpList toRlpList() { + List storageKeysRLPList = new ArrayList<>(); + for (String storageKey : getStorageKeys()) { + storageKeysRLPList.add(RlpString.create(Numeric.hexStringToByteArray(storageKey))); + } + return new RlpList( + RlpString.create(Numeric.hexStringToByteArray(getAddress())), + new RlpList(storageKeysRLPList) + ); + } + + /** + * Returns an encoded access tuple. + * + * @return byte[] + */ + public byte[] encodeToBytes() { + RlpList rlPList = this.toRlpList(); + return RlpEncoder.encode(rlPList); + } + + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param o The reference object with which to compare. + * @return boolean + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AccessTuple that = (AccessTuple) o; + + if (!address.equalsIgnoreCase(that.address)) { + return false; + } + if (storageKeys.size() != that.storageKeys.size()) { + return false; + } + for (int i = 0; i < storageKeys.size(); i++) { + if (!storageKeys.get(i).equalsIgnoreCase(that.storageKeys.get(i))) { + return false; + } + } + return true; + } + + public static class AccessTupleDeserializer extends JsonDeserializer { + @Override + public AccessTuple deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode root = p.getCodec().readTree(p); + JsonNode addressNode = root.get("address"); + String address = (addressNode == null ? "" : addressNode.textValue()); + List storageKeys = new ArrayList<>(); + Iterator storageKeysNodeIterator = root.get("storageKeys").iterator(); + while (storageKeysNodeIterator.hasNext()) { + storageKeys.add(storageKeysNodeIterator.next().textValue()); + } + return new AccessTuple(address, storageKeys); + } + } +} diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java new file mode 100644 index 000000000..0d2521054 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessListWrapper.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.utils.wrapper; + +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.web3j.rlp.RlpList; + +import java.util.List; + +/** + * Represents a AccessListWrapper + * 1. This class wraps all of static methods of AccessList + * 2. This class should be accessed via `caver.transaction.utils.accessList` + */ +public class AccessListWrapper { + /** + * Creates a AccessListWrapper instance. + */ + public AccessListWrapper() { + } + + /** + * Creates a AccessList instance derived from a RLP-encoded AccessList string. + * + * @param items A list of AccessTuple + * @return AccessList + */ + public AccessList create(List items) { + return AccessList.create(items); + } + + /** + * Decodes a RLP-encoded AccessList byte array. + * + * @param rlpEncoded RLP-encoded AccessList byte array. + * @return AccessList + */ + public AccessList decode(String rlpEncoded) { + return AccessList.decode(rlpEncoded); + } + + /** + * Decodes a RLP-encoded AccessList byte array. + * + * @param rlpEncoded RLP-encoded AccessList byte array. + * @return AccessList + */ + public AccessList decode(byte[] rlpEncoded) { + return AccessList.decode(rlpEncoded); + } + + + /** + * Decodes a RLP-encoded AccessList. + * + * @param rlpTypeList List of RlpType to decode. + * @return AccessList + */ + public AccessList decode(RlpList rlpTypeList) { + return AccessList.decode(rlpTypeList); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java new file mode 100644 index 000000000..cded38450 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/AccessTupleWrapper.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.utils.wrapper; + +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.web3j.rlp.RlpList; + +import java.util.List; + +/** + * Represents a AccessTupleWrapper + * 1. This class wraps all of static methods of AccessTuple + * 2. This class should be accessed via `caver.transaction.utils.accessTuple` + */ +public class AccessTupleWrapper { + /** + * Creates a AccessTupleWrapper instance. + */ + public AccessTupleWrapper() { + } + + /** + * Creates an AccessTuple instance. + * @param address An address string. + * @param storageKeys A list of storage keys. + * @return + */ + public AccessTuple create(String address, List storageKeys) { + return AccessTuple.create(address, storageKeys); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/TransactionUtilsWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/TransactionUtilsWrapper.java new file mode 100644 index 000000000..3d17e4b38 --- /dev/null +++ b/core/src/main/java/com/klaytn/caver/transaction/utils/wrapper/TransactionUtilsWrapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.transaction.utils.wrapper; + +/** + * TransactionUtilsWrapper provide usability so that the static method of the access list and the static method of the access tuple

+ * can be accessed and used through `caver.transaction.utils`. + */ +public class TransactionUtilsWrapper { + /** + * AccessListWrapper instance. + */ + public AccessListWrapper accessList; + + /** + * AccessTupleWrapper instance. + */ + public AccessTupleWrapper accessTuple; + + /** + * Creates a TransactionUtils instance. + */ + public TransactionUtilsWrapper() { + this.accessList = new AccessListWrapper(); + this.accessTuple = new AccessTupleWrapper(); + } +} diff --git a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java index 52e73b1d3..7d7c6a556 100644 --- a/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java +++ b/core/src/main/java/com/klaytn/caver/transaction/wrapper/TransactionWrapper.java @@ -21,6 +21,7 @@ import com.klaytn.caver.transaction.TransactionDecoder; import com.klaytn.caver.transaction.TransactionHelper; import com.klaytn.caver.transaction.type.wrapper.*; +import com.klaytn.caver.transaction.utils.wrapper.TransactionUtilsWrapper; import java.util.List; @@ -38,6 +39,16 @@ public class TransactionWrapper { */ public LegacyTransactionWrapper legacyTransaction; + /** + * EthereumAccessListWrapper instance + */ + public EthereumAccessListWrapper ethereumAccessList; + + /** + * EthereumDynamicFeeWrapper instance + */ + public EthereumDynamicFeeWrapper ethereumDynamicFee; + /** * ValueTransferWrapper instance */ @@ -143,6 +154,11 @@ public class TransactionWrapper { */ public FeeDelegatedChainDataAnchoringWithRatioWrapper feeDelegatedChainDataAnchoringWithRatio; + /** + * TransactionUtils instance + */ + public TransactionUtilsWrapper utils; + /** * Creates a Transaction instance * @param klaytnCall Klay RPC instance @@ -151,6 +167,8 @@ public TransactionWrapper(Klay klaytnCall) { this.klay = klaytnCall; this.legacyTransaction = new LegacyTransactionWrapper(klaytnCall); + this.ethereumAccessList = new EthereumAccessListWrapper(klaytnCall); + this.ethereumDynamicFee = new EthereumDynamicFeeWrapper(klaytnCall); this.valueTransfer = new ValueTransferWrapper(klaytnCall); this.feeDelegatedValueTransfer = new FeeDelegatedValueTransferWrapper(klaytnCall); @@ -179,6 +197,8 @@ public TransactionWrapper(Klay klaytnCall) { this.chainDataAnchoring = new ChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoring = new FeeDelegatedChainDataAnchoringWrapper(klaytnCall); this.feeDelegatedChainDataAnchoringWithRatio = new FeeDelegatedChainDataAnchoringWithRatioWrapper(klaytnCall); + + this.utils = new TransactionUtilsWrapper(); } /** diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java index 30594567a..a87668ec5 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/AbstractKeyring.java @@ -55,6 +55,23 @@ public AbstractKeyring(String address) { */ abstract public SignatureData sign(String txHash, int chainId, int role, int index); + /** + * Signs a transaction hash with all private keys in specific role group and returns signature list. + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return List<SignatureData> + */ + abstract public List ecsign(String txHash, int role); + + /** + * Signs a transaction hash with a private key in specific role group and returns signature + * @param txHash The hash transaction + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return SignatureData + */ + abstract public SignatureData ecsign(String txHash, int role, int index); + /** * Signs a hashed data with all private keys in specific role group and return MessageSigned instance. * @param message The data string to sign diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java index 5f5d0be0b..380c6c776 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/MultipleKeyring.java @@ -82,6 +82,57 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { return signatureData; } + /** + * Signs a transaction hash with all private keys in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * MultipleKeyring doesn't define the role group, so it signs a transaction using all keys existed in MultipleKeyring. + * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * MultipleKeyring keyring = new MultipleKeyring(.....);
+     * List signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return + */ + @Override + public List ecsign(String txHash, int role) { + PrivateKey[] keyArr = getKeyByRole(role); + + return Arrays.stream(keyArr) + .map(key-> { + return key.ecsign(txHash); + }).collect(Collectors.toList()); + } + + /** + * Signs a transaction hash with key in specific role group and returns a signature. + * MultipleKeyring doesn't define the role group, so it signs a transaction using specific key existed in MultipleKeyring. + * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *
Example :
+     * {@code
+     * MultipleKeyring keyring = new MultipleKeyring(.....);
+     * SignatureData signature = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * }
+     * 
+ * + * @param txHash The hash transaction + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return + */ + @Override + public SignatureData ecsign(String txHash, int role, int index) { + validatedIndexWithKeys(index, this.keys.length); + + PrivateKey key = getKeyByRole(role)[index]; + SignatureData signatureData = key.ecsign(txHash); + + return signatureData; + } + /** * Signs a hashed data with all key in specific role group and return MessageSigned instance. * @param message The data string to sign diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java index 0e62c0c18..a28013a7d 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/PrivateKey.java @@ -95,6 +95,31 @@ public SignatureData sign(String sigHash, int chainId) { return signData; } + /** + * Signs with hashed data and returns signature data.

+ * It returns a signature which has v as a parity of the y value(0 for even, 1 for odd) of secp256k1 signature. + *

Example :
+     * {@code
+     * String hash = "0x{hash}";
+     * PrivateKey prvKey = new PrivateKey("{privateKeyString}");
+     *
+     * SignatureData sign = prvKey.ecsign(hash);
+     * }
+     * 
+ * @param sigHash The hash to sign + * @return SignatureData + */ + public SignatureData ecsign(String sigHash) { + ECKeyPair keyPair = ECKeyPair.create(Numeric.toBigInt(privateKey)); + Sign.SignatureData signatureData = Sign.signMessage(Numeric.hexStringToByteArray(sigHash), keyPair, false); + + // Sign.signMessage() always add to 27 at V value. so it need to substract 27 from V value. + byte[] v = new byte[] {(byte)(signatureData.getV()[0] - 27)}; + + SignatureData signData = new SignatureData(v, signatureData.getR(), signatureData.getS()); + return signData; + } + /** * Signs with hashed data and returns signature * @param messageHash The hash of data to sign diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java index 6664939ea..35b405997 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/RoleBasedKeyring.java @@ -84,6 +84,56 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { return signatureData; } + /** + * Signs a transaction hash with all private keys in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * RoleBasedKeyring keyring = new RoleBasedKeyring(.....);
+     * List signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return + */ + @Override + public List ecsign(String txHash, int role) { + PrivateKey[] keyArr = getKeyByRole(role); + + return Arrays.stream(keyArr) + .map(key-> { + return key.ecsign(txHash); + }).collect(Collectors.toList()); + } + + /** + * Signs a transaction hash with key in specific role group and returns a signature.

+ * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * RoleBasedKeyring keyring = new RoleBasedKeyring(.....);
+     * SignatureData signature = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * }
+     * 
+ * + * @param txHash The hash transaction + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return + */ + @Override + public SignatureData ecsign(String txHash, int role, int index) { + PrivateKey[] keyArr = getKeyByRole(role); + validatedIndexWithKeys(index, keyArr.length); + + PrivateKey key = keyArr[index]; + SignatureData signatureData = key.ecsign(txHash); + + return signatureData; + } + /** * Signs a hashed data with all key in specific role group and return MessageSigned instance. * @param message The data string to sign diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java index 9b5186eb6..d897822e9 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SignatureData.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.utils.BytesUtils; import com.klaytn.caver.utils.Utils; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; @@ -68,6 +70,10 @@ public SignatureData(String v, String r, String s) { * @param s The ECDSA Signature data S */ public SignatureData(byte[] v, byte[] r, byte[] s) { + if (v.length == 0) { + // It handles when given v value was 0(represented "0x80" in rlp encoded). + v = BytesUtils.concat(new byte[]{0x0}, v); + } this.v = Numeric.toHexString(v); this.r = Numeric.toHexString(r); this.s = Numeric.toHexString(s); @@ -160,8 +166,19 @@ public RlpList toRlpList() { byte[] r = Numeric.hexStringToByteArray(getR()); byte[] s = Numeric.hexStringToByteArray(getS()); + RlpString rlpV; + byte[] trimmedV = Bytes.trimLeadingZeroes(v); + if (trimmedV[0] == 0) { + // If v value is "0x0", the shape of trimmedV is [0x0] (have 1 element). + // If we create rlpV by using RlpString.create(byte[]{0x0}), it is encoded as `0x00` but + // v value is integer by its spec and integer 0 must be encoded as 0x80. + rlpV = RlpString.create(BigInteger.valueOf(0)); + } else { + rlpV = RlpString.create(trimmedV); + } + return new RlpList( - RlpString.create(Bytes.trimLeadingZeroes(v)), + rlpV, RlpString.create(Bytes.trimLeadingZeroes(r)), RlpString.create(Bytes.trimLeadingZeroes(s)) ); @@ -175,6 +192,12 @@ public RlpList toRlpList() { public int getRecoverId() { int v = Numeric.toBigInt(this.getV()).intValue() & 0xFFFF; + // The v value of EthereumAccessList or EthereumDynamicFee transaction has 0 or 1(parity value of y value in Signature). + // It should be accepted 0 or 1 value + if (v == 0 || v == 1) { + return v; + } + if (v < 27) { throw new RuntimeException("v byte out of range: " + v); } diff --git a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java index 6c3cda645..dbebf0c14 100644 --- a/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java +++ b/core/src/main/java/com/klaytn/caver/wallet/keyring/SingleKeyring.java @@ -78,6 +78,56 @@ public SignatureData sign(String txHash, int chainId, int role, int index) { return data; } + /** + * Signs a transaction hash with private key in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * SingleKeyring doesn't define the role group, so it signs a transaction using key existed in SingleKeyring. + * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}. + *

Example :
+     * {@code
+     * SingleKeyring keyring = new SingleKeyring(.....);
+     * List signatureList = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @return + */ + @Override + public List ecsign(String txHash, int role) { + PrivateKey key = getKeyByRole(role); + SignatureData data = key.ecsign(txHash); + + return Arrays.asList(data); + } + + /** + * Signs a transaction hash with private key in specific role group and returns a signature list which V is 0 or 1(parity of the y-value of a secp256k1 signature).

+ * SingleKeyring doesn't define the role group and only have one private key, so it signs a transaction using key existed in SingleKeyring.

+ * The role used in caver-java can be checked through {@link com.klaytn.caver.account.AccountKeyRoleBased.RoleGroup}.

+ * The index value must 0. + *

Example :
+     * {@code
+     * SingleKeyring keyring = new SingleKeyring(.....);
+     * SignatureData signature = keyring.ecsign("0xe9a11d9ef95fb437f75d07ce768d43e74f158dd54b106e7d3746ce29d545b550", AccountKeyRoleBased.RoleGroup.TRANSACTION, 0);
+     * }
+     * 
+ * + * @param txHash The hash of transaction. + * @param role A number indicating the role of the key. + * @param index The index of the key to be used in the specific role group. + * @return + */ + @Override + public SignatureData ecsign(String txHash, int role, int index) { + validatedIndexWithKeys(index, 1); + + PrivateKey key = getKeyByRole(role); + SignatureData data = key.ecsign(txHash); + + return data; + } + /** * Signs a hashed data with all key in specific role group and return MessageSigned instance. * @param message The data string to sign diff --git a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java new file mode 100644 index 000000000..d57dd8ee4 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionReceiptTest.java @@ -0,0 +1,114 @@ +package com.klaytn.caver.common.methods.response; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.methods.response.TransactionReceipt; +import org.junit.Test; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +public class TransactionReceiptTest { + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + public static class ethereumAccessListTest { + @Test + public void deserializeTest() throws IOException { + String ethereumAccessListReceiptJson = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x43459f308ba7e54976491922c062cad07c70798a24de403ee830f2c36f0eb59e\",\n" + + " \"blockNumber\": 91,\n" + + " \"chainID\": \"0x7e3\",\n" + + " \"contractAddress\": null,\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": \"0xf423f\",\n" + + " \"gasPrice\": \"0x5d21dba00\",\n" + + " \"gasUsed\": 25300,\n" + + " \"input\": \"0x\",\n" + + " \"logs\": [],\n" + + " \"logsBloom\": \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"senderTxHash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"R\": \"0x1c064e8434def2a74b129dfaa5bd98e8fb8402fc5db284ecdd6807351681ba75\",\n" + + " \"S\": \"0x3515882ee8d0d3a59c3075eca9748695f0a3e9a7f52b7e4987950eef52bbb42\",\n" + + " \"V\": \"0x1\"\n" + + " }\n" + + " ],\n" + + " \"status\": \"0x1\",\n" + + " \"to\": \"0x8c9f4468ae04fb3d79c80f6eacf0e4e1dd21deee\",\n" + + " \"transactionHash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"transactionIndex\": 0,\n" + + " \"type\": \"TxTypeEthereumAccessList\",\n" + + " \"typeInt\": 30721,\n" + + " \"value\": \"0x1\"\n" + + "}"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumAccessListReceiptJson); + TransactionReceipt.TransactionReceiptData transactionReceiptData = objectMapper.readValue(reader, TransactionReceipt.TransactionReceiptData.class); + System.out.println(objectToString(transactionReceiptData)); + } + } + + public static class ethereumDynamicFeeTest { + @Test + public void deserializeTest() throws IOException { + String ethereumDynamicFeeReceiptJson = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x2cb63fb3f9764280061a40fa6993b5281ae775f22ae71db045d05f2b90f1c9d0\",\n" + + " \"blockNumber\": \"0xabc1\",\n" + + " \"chainId\": \"0x7e3\",\n" + + " \"contractAddress\": null,\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": \"0x7a120\",\n" + + " \"gasUsed\": \"0x6a40\",\n" + + " \"input\": \"0x\",\n" + + " \"logs\": [],\n" + + " \"logsBloom\": \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n" + + " \"maxFeePerGas\": \"0x5d21dba00\",\n" + + " \"maxPriorityFeePerGas\": \"0x5d21dba00\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"senderTxHash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"V\": \"0x0\",\n" + + " \"R\": \"0xef11e538b4ae74704c26d0d23da0d93fea4ca65a1d9a924819b43fa6aeee3923\",\n" + + " \"S\": \"0x2456ffa6da778525e44634306a81ada9effbbc2ab63c272a8ae208baac4e3deb\"\n" + + " }\n" + + " ],\n" + + " \"status\": \"0x1\",\n" + + " \"to\": \"0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499\",\n" + + " \"transactionHash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"transactionIndex\": \"0x0\",\n" + + " \"type\": \"TxTypeEthereumDynamicFee\",\n" + + " \"typeInt\": 30722,\n" + + " \"value\": \"0x1\"\n" + + "}"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumDynamicFeeReceiptJson); + TransactionReceipt.TransactionReceiptData transactionReceiptData = objectMapper.readValue(reader, TransactionReceipt.TransactionReceiptData.class); + System.out.println(objectToString(transactionReceiptData)); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java new file mode 100644 index 000000000..f709e3280 --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/methods/response/TransactionTest.java @@ -0,0 +1,109 @@ +package com.klaytn.caver.common.methods.response; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.methods.response.Transaction; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +@RunWith(Enclosed.class) +public class TransactionTest { + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + public static class ethereumAccessListTest { + @Test + public void deserializeTest() throws IOException { + String ethereumAccessListJson = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x43459f308ba7e54976491922c062cad07c70798a24de403ee830f2c36f0eb59e\",\n" + + " \"blockNumber\": 91,\n" + + " \"chainID\": \"0x7e3\",\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": 999999,\n" + + " \"gasPrice\": 25000000000,\n" + + " \"hash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"input\": \"0x\",\n" + + " \"nonce\": 0,\n" + + " \"senderTxHash\": \"0x658b0aa2b625a1a0c3c9fe54e7f5f51ba962aeb7e62ae2ed338597058a603d71\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"R\": \"0x1c064e8434def2a74b129dfaa5bd98e8fb8402fc5db284ecdd6807351681ba75\",\n" + + " \"S\": \"0x3515882ee8d0d3a59c3075eca9748695f0a3e9a7f52b7e4987950eef52bbb42\",\n" + + " \"V\": \"0x1\"\n" + + " }\n" + + " ],\n" + + " \"to\": \"0x8c9f4468ae04fb3d79c80f6eacf0e4e1dd21deee\",\n" + + " \"transactionIndex\": 0,\n" + + " \"type\": \"TxTypeEthereumAccessList\",\n" + + " \"typeInt\": 30721,\n" + + " \"value\": 1\n" + + "}"; + + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumAccessListJson); + Transaction.TransactionData transactionData = objectMapper.readValue(reader, Transaction.TransactionData.class); + System.out.println(objectToString(transactionData)); + } + } + + public static class ethereumDynamicFeeTest { + + @Test + public void deserializeTest() throws IOException { + String ethereumDynamicFee = "{\n" + + " \"accessList\": [\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"blockHash\": \"0x2cb63fb3f9764280061a40fa6993b5281ae775f22ae71db045d05f2b90f1c9d0\",\n" + + " \"blockNumber\": \"0xabc1\",\n" + + " \"chainId\": \"0x7e3\",\n" + + " \"from\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"gas\": \"0x7a120\",\n" + + " \"hash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"input\": \"0x\",\n" + + " \"maxFeePerGas\": \"0x5d21dba00\",\n" + + " \"maxPriorityFeePerGas\": \"0x5d21dba00\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"senderTxHash\": \"0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19\",\n" + + " \"signatures\": [\n" + + " {\n" + + " \"V\": \"0x0\",\n" + + " \"R\": \"0xef11e538b4ae74704c26d0d23da0d93fea4ca65a1d9a924819b43fa6aeee3923\",\n" + + " \"S\": \"0x2456ffa6da778525e44634306a81ada9effbbc2ab63c272a8ae208baac4e3deb\"\n" + + " }\n" + + " ],\n" + + " \"to\": \"0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499\",\n" + + " \"transactionIndex\": \"0x0\",\n" + + " \"type\": \"TxTypeEthereumDynamicFee\",\n" + + " \"typeInt\": 30722,\n" + + " \"value\": \"0x1\"\n" + + " }"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(ethereumDynamicFee); + Transaction.TransactionData transactionData = objectMapper.readValue(reader, Transaction.TransactionData.class); + System.out.println(objectToString(transactionData)); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java index c90a74df6..1ac320a85 100644 --- a/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/common/rpc/RpcTest.java @@ -29,16 +29,18 @@ import com.klaytn.caver.methods.response.Account; import com.klaytn.caver.methods.response.Boolean; import com.klaytn.caver.rpc.Klay; +import com.klaytn.caver.transaction.TxPropertyBuilder; import com.klaytn.caver.transaction.response.PollingTransactionReceiptProcessor; import com.klaytn.caver.transaction.response.TransactionReceiptProcessor; -import com.klaytn.caver.transaction.type.FeeDelegatedValueTransfer; -import com.klaytn.caver.transaction.type.FeeDelegatedValueTransferWithRatio; -import com.klaytn.caver.transaction.type.ValueTransfer; +import com.klaytn.caver.transaction.type.*; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.utils.Convert; import com.klaytn.caver.utils.Utils; import com.klaytn.caver.wallet.keyring.AbstractKeyring; import com.klaytn.caver.wallet.keyring.KeyringFactory; import com.klaytn.caver.wallet.keyring.SingleKeyring; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -46,19 +48,18 @@ import org.junit.runner.RunWith; import org.web3j.protocol.ObjectMapperFactory; import org.web3j.protocol.Web3jService; -import org.web3j.protocol.core.DefaultBlockParameterName; -import org.web3j.protocol.core.DefaultBlockParameterNumber; -import org.web3j.protocol.core.Request; -import org.web3j.protocol.core.Response; +import org.web3j.protocol.core.*; import org.web3j.protocol.exceptions.TransactionException; import org.web3j.utils.Numeric; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import static com.klaytn.caver.base.LocalValues.LOCAL_CHAIN_ID; import static com.klaytn.caver.base.LocalValues.LOCAL_NETWORK_ID; @@ -263,6 +264,182 @@ public void valueTransferTest() throws IOException { } } + public static class signEthereumTransactionTest { + static String klayProvider = "871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5"; + static SingleKeyring klayProviderKeyring = KeyringFactory.createFromPrivateKey(klayProvider); + + @BeforeClass + public static void unlockAccount() throws IOException { + RpcTest.importKey(klayProvider, "mypassword"); + RpcTest.unlockAccount(klayProviderKeyring.getAddress(), "mypassword"); + } + + String to = "0x7b65B75d204aBed71587c9E519a89277766EE1d0"; + String gas = "0xf4240"; + String gasPrice = "0x5d21dba00"; + String nonce; + String value = "0xa"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + @Test + public void ethereumAccessListTest() throws IOException { + nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setTo(to) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumAccessList.sign(klayProviderKeyring); + + SignTransaction signTransaction = klay.signTransaction(ethereumAccessList).send(); + assertEquals(signTransaction.getResult().getRaw(), ethereumAccessList.getRLPEncoding()); + } + + @Test + public void ethereumDynamicFeeTest() throws IOException { + nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setTo(to) + .setGas(gas) + .setMaxPriorityFeePerGas(gasPrice) + .setMaxFeePerGas(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumDynamicFee.sign(klayProviderKeyring); + + SignTransaction signTransaction = klay.signTransaction(ethereumDynamicFee).send(); + assertEquals(signTransaction.getResult().getRaw(), ethereumDynamicFee.getRLPEncoding()); + } + } + + public static class sendAndGetEthereumTransactionTest { + static String klayProvider = "871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5"; + static SingleKeyring klayProviderKeyring = KeyringFactory.createFromPrivateKey(klayProvider); + + @BeforeClass + public static void unlockAccount() throws IOException { + RpcTest.importKey(klayProvider, "mypassword"); + RpcTest.unlockAccount(klayProviderKeyring.getAddress(), "mypassword"); + } + + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String gas = "0xf4240"; + String gasPrice = "0x5d21dba00"; + String value = "0xa"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + private void checkTransactionFields(Transaction.TransactionData transactionData, boolean isDynamicFee) { + Assert.assertEquals(to, transactionData.getTo()); + Assert.assertEquals(gas, transactionData.getGas()); + if (isDynamicFee) { + Assert.assertEquals(gasPrice, transactionData.getMaxPriorityFeePerGas()); + Assert.assertEquals(gasPrice, transactionData.getMaxFeePerGas()); + } else { + Assert.assertEquals(gasPrice, transactionData.getGasPrice()); + } + Assert.assertEquals(accessList, transactionData.getAccessList()); + } + + private void checkTransactionReceiptFields(TransactionReceipt.TransactionReceiptData transactionReceiptData, boolean isDynamicFee) { + Assert.assertEquals(to, transactionReceiptData.getTo()); + Assert.assertEquals(gas, transactionReceiptData.getGas()); + if (isDynamicFee) { + Assert.assertEquals(gasPrice, transactionReceiptData.getMaxPriorityFeePerGas()); + Assert.assertEquals(gasPrice, transactionReceiptData.getMaxFeePerGas()); + } else { + Assert.assertEquals(gasPrice, transactionReceiptData.getGasPrice()); + } + Assert.assertEquals(accessList, transactionReceiptData.getAccessList()); + } + + @Test + public void ethereumAccessListTest() throws IOException, InterruptedException { + String nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setTo(to) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumAccessList.sign(klayProviderKeyring); + + Bytes32 result = klay.sendTransaction(ethereumAccessList).send(); + String transactionHash = result.getResult(); + Thread.sleep(2000); + Transaction transaction = klay.getTransactionByHash(transactionHash).send(); + checkTransactionFields(transaction.getResult(), false); + TransactionReceipt transactionReceipt = klay.getTransactionReceipt(transactionHash).send(); + checkTransactionReceiptFields(transactionReceipt.getResult(), false); + Assert.assertEquals(transactionHash, transactionReceipt.getResult().getTransactionHash()); + } + + + @Test + public void ethereumDynamicFeeTest() throws IOException, InterruptedException { + String nonce = klay.getTransactionCount(klayProviderKeyring.getAddress(), DefaultBlockParameterName.PENDING).send().getResult(); + String chainId = klay.getChainID().send().getResult(); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setTo(to) + .setGas(gas) + .setMaxPriorityFeePerGas(gasPrice) + .setMaxFeePerGas(gasPrice) + .setChainId(chainId) + .setNonce(nonce) + .setValue(value) + .setAccessList(accessList) + .build(); + + ethereumDynamicFee.sign(klayProviderKeyring); + + Bytes32 result = klay.sendTransaction(ethereumDynamicFee).send(); + String transactionHash = result.getResult(); + Thread.sleep(2000); + Transaction transaction = klay.getTransactionByHash(transactionHash).send(); + checkTransactionFields(transaction.getResult(), true); + TransactionReceipt transactionReceipt = klay.getTransactionReceipt(transactionHash).send(); + checkTransactionReceiptFields(transactionReceipt.getResult(), true); + Assert.assertEquals(transactionHash, transactionReceipt.getResult().getTransactionHash()); + } + } + public static class signTransactionAsFeePayerTest { static String klayProvider = "871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5"; static String feePayer = "1e558ea00698990d875cb69d3c8f9a234fe8eab5c6bd898488d851669289e178"; @@ -464,8 +641,8 @@ public static class otherRPCTest { static TransactionReceipt.TransactionReceiptData sampleReceiptData; private static TransactionReceipt.TransactionReceiptData sendKlay() throws IOException, TransactionException { - Caver caver = new Caver(Caver.DEFAULT_URL); - AbstractKeyring keyring = caver.wallet.add(KeyringFactory.createFromPrivateKey("0x871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5")); + Caver caver = new Caver(Caver.DEFAULT_URL); + AbstractKeyring keyring = caver.wallet.add(KeyringFactory.createFromPrivateKey("0x871ccee7755bb4247e783110cafa6437f9f593a1eaeebe0efcc1b0852282c3e5")); BigInteger value = new BigInteger(Utils.convertToPeb(BigDecimal.ONE, "KLAY")); @@ -592,6 +769,62 @@ public void getBlockNumberTest() { } } + @Test + public void getHeaderTest() { + try { + BlockHeader response = caver.rpc.klay.getHeader(DefaultBlockParameterName.LATEST).send(); + BlockHeader.BlockHeaderData blockHeader = response.getResult(); + String hash = blockHeader.getHash(); + assertNotNull(hash); + + response = caver.rpc.klay.getHeader(BigInteger.valueOf(0)).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + + response = caver.rpc.klay.getHeader(hash).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void getHeaderByNumberTest() { + try { + BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST).send(); + BlockHeader.BlockHeaderData blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + + response = caver.rpc.klay.getHeaderByNumber(new DefaultBlockParameterNumber(0)).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + + response = caver.rpc.klay.getHeaderByNumber(BigInteger.valueOf(0)).send(); + blockHeader = response.getResult(); + assertNotNull(blockHeader.getHash()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void getHeaderByHashTest() { + try { + BlockHeader response = caver.rpc.klay.getHeaderByNumber(DefaultBlockParameterName.LATEST).send(); + BlockHeader.BlockHeaderData blockHeader = response.getResult(); + + BlockHeader responseByHash = caver.rpc.klay.getHeaderByHash(blockHeader.getHash()).send(); + + assertEquals(blockHeader.getHash(), responseByHash.getResult().getHash()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + @Test public void getBlockByNumberTest() { try { @@ -748,7 +981,7 @@ public void estimateGasTest() throws Exception { ); Quantity response = caver.rpc.klay.estimateGas(callObject).send(); String result = response.getResult(); - assertEquals("0xb2d9", result); + assertEquals("0xcb09", result); } @Test @@ -765,7 +998,7 @@ public void estimateComputationCostTest() throws Exception { ); Quantity response = caver.rpc.klay.estimateComputationCost(callObject, DefaultBlockParameterName.LATEST).send(); String result = response.getResult(); - assertEquals("0xe036", result); + assertEquals("0xd949", result); } @Test @@ -839,6 +1072,119 @@ public void getGasPriceAtTest() throws IOException { assertEquals(new BigInteger("5d21dba00", 16), result); // 25,000,000,000 peb = 25 Gpeb } + @Test + public void getMaxPriorityFeePerGasTest() throws Exception { + Quantity response = caver.rpc.klay.getMaxPriorityFeePerGas().send(); + BigInteger result = response.getValue(); + assertEquals(new BigInteger("5d21dba00", 16), result); // 25,000,000,000 peb = 25 Gpeb + } + + // checkFeeHistoryResult checks response from getFeeHistory is ok or not. + private void checkFeeHistoryResult(FeeHistoryResult feeHistoryResult, long blockCount, List rewardPercentiles) { + FeeHistoryResult.FeeHistoryResultData feeHistoryResultData = feeHistoryResult.getResult(); + assertEquals(blockCount + 1, feeHistoryResultData.getBaseFeePerGas().size()); + + List> reward = feeHistoryResultData.getReward(); + if (reward != null) { + assertEquals(blockCount, reward.size()); + Consumer> consumer = rewardElement -> { + int expectedSize = 0 ; + if (rewardPercentiles != null) { + expectedSize = rewardPercentiles.size(); + } + assertEquals(expectedSize, rewardElement.size()); + }; + reward.forEach(consumer); + } + + assertEquals(blockCount, feeHistoryResultData.getGasUsedRatio().size()); + } + + @Test + public void getFeeHistoryTest() throws IOException, InterruptedException { + SingleKeyring sender = caver.wallet.keyring.createFromKlaytnWalletKey(LUMAN.getKlaytnWalletKey()); + caver.wallet.add(sender); + Quantity transactionCount = caver.rpc.klay.getTransactionCount(sender.getAddress()).send(); + BigInteger nonce = transactionCount.getValue(); + String gasPrice = klay.getGasPrice().send().getResult(); + + String chainId = klay.getChainID().send().getResult(); + final int txsCount = 30; + TransactionReceipt.TransactionReceiptData receiptData = null; + for (int i = 0; i < txsCount; i++) { + ValueTransfer tx = caver.transaction.valueTransfer.create( + TxPropertyBuilder.valueTransfer() + .setFrom(sender.getAddress()) + .setTo(caver.wallet.keyring.generate().getAddress()) + .setGas("0x99999") + .setGasPrice(gasPrice) + .setValue("0x1") + .setNonce(nonce) + .setChainId(chainId) + ); + caver.wallet.sign(sender.getAddress(), tx); + nonce = nonce.add(BigInteger.valueOf(1)); + + if (i != txsCount - 1) { + caver.rpc.klay.sendRawTransaction(tx).send(); + continue; + } + Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(tx).send(); + Thread.sleep(5000); + String txHash = sendResult.getResult(); + TransactionReceipt receipt = caver.rpc.klay.getTransactionReceipt(txHash).send(); + receiptData = receipt.getResult(); + } + if (receiptData == null) { + fail(); + } + + long blockCount = 5; + long blockNumber = new BigInteger(caver.utils.stripHexPrefix(receiptData.getBlockNumber()), 16).longValue(); + List rewardPercentiles = new ArrayList(Arrays.asList(0.3f, 0.5f, 0.8f)); + FeeHistoryResult feeHistoryResult = caver.rpc.klay.getFeeHistory(blockCount, blockNumber, rewardPercentiles).send(); + checkFeeHistoryResult(feeHistoryResult, blockCount, rewardPercentiles); + + blockCount = 5; + rewardPercentiles = null; + feeHistoryResult = caver.rpc.klay.getFeeHistory(blockCount, DefaultBlockParameterName.LATEST, rewardPercentiles).send(); + checkFeeHistoryResult(feeHistoryResult, blockCount, rewardPercentiles); + } + + @Test + public void createAccessListTest() throws IOException { + Block block = caver.rpc.klay.getBlockByNumber(DefaultBlockParameterName.LATEST).send(); + Quantity gasPrice = caver.rpc.klay.getGasPrice().send(); + BigInteger blockNumber = new BigInteger(caver.utils.stripHexPrefix(block.getResult().getNumber()), 16); + String blockHash = block.getResult().getHash(); + CallObject callObject = CallObject.createCallObject( + LUMAN.getAddress(), + WAYNE.getAddress(), + BigInteger.valueOf(100000), + gasPrice.getValue(), + BigInteger.valueOf(1) + ); + AccessListResult accessListResult = caver.rpc.klay.createAccessList(callObject, DefaultBlockParameterName.LATEST).send(); + checkAccessListResult(accessListResult.getResult()); + + accessListResult = caver.rpc.klay.createAccessList(callObject, new DefaultBlockParameterNumber(blockNumber)).send(); + checkAccessListResult(accessListResult.getResult()); + + accessListResult = caver.rpc.klay.createAccessList(callObject, blockNumber).send(); + checkAccessListResult(accessListResult.getResult()); + + accessListResult = caver.rpc.klay.createAccessList(callObject, blockHash).send(); + checkAccessListResult(accessListResult.getResult()); + } + + // checkAccessListResult checks whether given AcccessListResultData instance is right or not. + private void checkAccessListResult(AccessListResult.AccessListResultData accessListResultData) { + + assertEquals(0, accessListResultData.getAccessList().size()); // For now Klaytn will return empty access list. + assertEquals("0x0", accessListResultData.getGasUsed()); // For now Klaytn will return zero gasUsed. + assertEquals(null, accessListResultData.getError()); + } + @Test public void isParallelDbWriteTest() throws Exception { Boolean response = caver.rpc.klay.isParallelDBWrite().send(); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java new file mode 100644 index 000000000..82d82a03a --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumAccessListTest.java @@ -0,0 +1,1713 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.common.transaction; + +import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TxPropertyBuilder; +import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.transaction.type.LegacyTransaction; +import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.junit.*; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(Enclosed.class) +public class EthereumAccessListTest { + + public static class createInstance { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + String nonce = "0x4D2"; + String gas = "0xf4240"; + String gasPrice = "0x19"; + String from = "0x"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + )) + ); + + Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void createInstance() throws IOException { + AbstractKeyring keyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(keyring.getAddress()) + .setNonce("0x") + .setGas(gas) + .setGasPrice("0x") + .setChainId("0x") + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + + assertNotNull(ethereumAccessList); + + ethereumAccessList.fillTransaction(); + assertNotNull(ethereumAccessList.getNonce()); + assertNotNull(ethereumAccessList.getGasPrice()); + assertNotNull(ethereumAccessList.getChainId()); + assertEquals(accessList, ethereumAccessList.getAccessList()); + assertEquals(TransactionType.TxTypeEthereumAccessList.toString(), ethereumAccessList.getType()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + String value = "invalid"; + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(from) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String to = "invalid"; + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(from) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(from) + .setNonce(nonce) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + } + + public static class createInstanceBuilder { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + static String nonce = "0x4D2"; + static String gas = "0xf4240"; + static String gasPrice = "0x19"; + static String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + static String chainID = "0x1"; + static String input = "0x31323334"; + static String value = "0xa"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + )) + ); + + @Test + public void BuilderTest() { + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertNotNull(ethereumAccessList); + assertEquals(TransactionType.TxTypeEthereumAccessList.toString(), ethereumAccessList.getType()); + } + + @Test + public void BuilderTestWithBigInteger() { + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(Numeric.toBigInt(gasPrice)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertEquals(gas, ethereumAccessList.getGas()); + assertEquals(gasPrice, ethereumAccessList.getGasPrice()); + assertEquals(chainID, ethereumAccessList.getChainId()); + assertEquals(value, ethereumAccessList.getValue()); + assertEquals(accessList, ethereumAccessList.getAccessList()); + } + + @Test + public void BuilderTestRPC() throws IOException { + String gas = "0x0f4240"; + String to = "7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0x0a"; + + Caver caver = new Caver(Caver.DEFAULT_URL); + AbstractKeyring keyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setKlaytnCall(caver.rpc.getKlay()) + .setGas(gas) + .setInput(input) + .setValue(value) + .setFrom(keyring.getAddress()) // For Test. It automatically filled when executed EthereumAccessList.signWithKey or signWithKeysTest. + .setTo(to) + .setAccessList(accessList) + .build(); + + ethereumAccessList.fillTransaction(); + assertNotNull(ethereumAccessList.getNonce()); + assertNotNull(ethereumAccessList.getGasPrice()); + assertNotNull(ethereumAccessList.getChainId()); + assertNotNull(ethereumAccessList.getAccessList()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("0x") + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_invalid_value2() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("invalid") + .build(); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String gas = "0xf4240"; + String to = "invalid"; + String input = "0x31323334"; + String value = "0xa"; + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumAccessList ethereumAccessList = new EthereumAccessList.Builder() + .setNonce(nonce) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + } + + public static class getRLPEncodingAndDecodingTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void getRLPEncodingAndDecoding() { + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + AccessList accessList1 = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList("0x0000000000000000000000000000000000000000000000000000000000000000")) + )); + AccessList accessList2 = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + AccessList accessList3 = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x0000000000000000000000000000000000000002", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + SignatureData signatureData1 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x5d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293"), + Numeric.hexStringToByteArray("0x1d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f") + ); + SignatureData signatureData2 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x19676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99"), + Numeric.hexStringToByteArray("0x70feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235") + ); + SignatureData signatureData3 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x3966e6b3297cbcfdbb3e1e3ede1b80dea99d9d01983fc32e337d785fe4445973"), + Numeric.hexStringToByteArray("0x49e25fcda7c8d5517181e2afcf13d2dff77dc33884b512fd1f1cd0770bcd0c59") + ); + SignatureData signatureData4 = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x5d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4"), + Numeric.hexStringToByteArray("0x782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb") + ); + + // Test-1: encoding EthereumAccessList which have an address and a storage key. + String expectedRLP = "0x7801f88701040a8301e241808080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a05d2368dff6ab943d3467558de70805d5b6a4367ab94b1ee8a7ae53e4e0f68293a01d68f38dce681c32cd001ca155b2b27e26ddd788b4bdaaf355181c626805ec7f"; + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setNonce("0x4") + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList1) + .setSignatures(signatureData1) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-1: decoding + EthereumAccessList decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // Test-2: encoding EthereumAccessList which have many storageKeys + expectedRLP = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(BigInteger.valueOf(4)) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList2) + .setSignatures(signatureData2) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-2: decoding + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // Test-3: encoding EthereumAccessList which have empty accessList + expectedRLP = "0x7801f86801030a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878086616263646566c001a03966e6b3297cbcfdbb3e1e3ede1b80dea99d9d01983fc32e337d785fe4445973a049e25fcda7c8d5517181e2afcf13d2dff77dc33884b512fd1f1cd0770bcd0c59"; + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(BigInteger.valueOf(3)) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(null) + .setInput(input) + .setSignatures(signatureData3) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-3: decoding + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // Test-4: encoding EthereumAccessList which have many accessList + expectedRLP = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setNonce(BigInteger.valueOf(4)) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList3) + .setSignatures(signatureData4) + ); + assertEquals(expectedRLP, ethereumAccessList.getRLPEncoding()); + // Test-4: decoding + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(expectedRLP); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + } + + @Test + public void getRLPEncodingAndDecodingYParity() { + // 0 y-parity + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo("0xc6779d72a88bec1a03bbb83cf028d95ff5f32f5b") + .setValue("0x1") + .setGas("0x9c40") + .setNonce("0x1a") + .setGasPrice("0x5d21dba00") + .setChainId("0x2710") + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(caver.transaction.utils.accessList.create(Arrays.asList( + caver.transaction.utils.accessTuple.create( + "0xac9ba2a7fb8572e971bcac01a5b58934b385a172", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + ) + ))) + .setSignatures(new SignatureData( + "0x00", + "0x43ff73938e019e13dcc48c9ff1a46d9f1f081512351cf7b0eca49dbf74047848", + "0x17a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af" + )) + ); + String rlpEncoded = ethereumAccessList.getRLPEncoding(); + Assert.assertEquals("0x7801f901098227101a8505d21dba00829c4094c6779d72a88bec1a03bbb83cf028d95ff5f32f5b01b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf85994ac9ba2a7fb8572e971bcac01a5b58934b385a172f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a043ff73938e019e13dcc48c9ff1a46d9f1f081512351cf7b0eca49dbf74047848a017a9816ca1446f51e0d6eb8c406a52758feb83b234128e4cfcaeaa8419f706af", rlpEncoded); + EthereumAccessList decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(rlpEncoded); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + + // 1 y-parity + ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo("0xc5fb1386b60160614a8151dcd4b0ae41325d1cb8") + .setValue("0x1") + .setGas("0x9c40") + .setNonce("0x23") + .setGasPrice("0x5d21dba00") + .setChainId("0x2710") + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(caver.transaction.utils.accessList.create(Arrays.asList( + caver.transaction.utils.accessTuple.create( + "0x5430192ae264b3feff967fc08982b9c6f5694023", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + ) + ))) + .setSignatures(new SignatureData( + "0x01", + "0x5ac25e47591243af2d6b8e7f54d608e9e0e0aeb5194d34c17852bd7e376f4857", + "0x095a40394f33e95cce9695d5badf4270f4cc8aff0b5395cefc3a0fe213be1f30" + )) + ); + + rlpEncoded = ethereumAccessList.getRLPEncoding(); + Assert.assertEquals("0x7801f90109822710238505d21dba00829c4094c5fb1386b60160614a8151dcd4b0ae41325d1cb801b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf859945430192ae264b3feff967fc08982b9c6f5694023f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a05ac25e47591243af2d6b8e7f54d608e9e0e0aeb5194d34c17852bd7e376f4857a0095a40394f33e95cce9695d5badf4270f4cc8aff0b5395cefc3a0fe213be1f30", ethereumAccessList.getRLPEncoding()); + decodedEthereumAccessList = (EthereumAccessList) caver.transaction.decode(rlpEncoded); + assertTrue(ethereumAccessList.compareTxField(decodedEthereumAccessList, true)); + } + + @Test + public void throwException_NoNonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + String gas = "0xf4240"; + String gasPrice = "0x19"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x1"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumAccessList.getRLPEncoding(); + } + + @Test + public void throwException_NoGasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumAccessList.getRLPEncoding(); + } + + @Test + public void throwException_NoChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String gasPrice = "0x10"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumAccessList.getRLPEncoding(); + } + + + } + + public static class getTransactionHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x0"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761", + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + static SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x26e8a6755fbf9d8d32271753c332718825881fef855e7f9e4868e2d16f908caa"), + Numeric.hexStringToByteArray("0x412c0e1dc1fe87fcc7b262be995701dc0606038844f8a0da1eaddef965d8fc2b") + ); + + @Test + public void getTransactionHash() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + String expected = "0x5f4484fe404d9e5b3c100d1b67aa5633daab5d516267bf8af03550644e2d7378"; + String txHash = ethereumAccessList.getTransactionHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumAccessList.getTransactionHash(); + } + + @Test + public void throwException_NotDefined_GasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getRawTransaction(); + } + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getTransactionHash(); + } + } + + public static class getSenderTxHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x0"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761", + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + static SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x26e8a6755fbf9d8d32271753c332718825881fef855e7f9e4868e2d16f908caa"), + Numeric.hexStringToByteArray("0x412c0e1dc1fe87fcc7b262be995701dc0606038844f8a0da1eaddef965d8fc2b") + ); + + @Test + public void getTransactionHash() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + String expected = "0x5f4484fe404d9e5b3c100d1b67aa5633daab5d516267bf8af03550644e2d7378"; + String txHash = ethereumAccessList.getSenderTxHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumAccessList.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_GasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setNonce(nonce) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumAccessList.getSenderTxHash(); + } + } + + public static class combineSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); +; + @Test + public void combineSignature() { + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String chainID = "0x1"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x0000000000000000000000000000000000000002", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x5d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4"), + Numeric.hexStringToByteArray("0x782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb") + ); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList) + ); + + // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. + String rlpEncoded = "0x7801f9013d01040a8301e241808080f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000000000002f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a05d6d9bc7bb01b05db25f5f2e4a995a4970124387293694f0fd8bdda95bc6e7f4a00782feaf0460341b320710ef7a4f07167d551fd897775af5ec2f1dea095e99cb"; + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumAccessList.combineSignedRawTransactions(rlpList); + + assertEquals(rlpEncoded, combined); + } + + @Test + public void combineSignature_EmptySig() { + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x19676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99"), + Numeric.hexStringToByteArray("0x70feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235") + ); + + SignatureData emptySig = SignatureData.getEmptySignature(); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList) + .setSignatures(emptySig) + ); + + // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumAccessList.combineSignedRawTransactions(rlpList); + + assertEquals(rlpEncoded, combined); + } + + @Test + public void throwException_existSignature() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0x19676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99"), + Numeric.hexStringToByteArray("0x70feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235") + ); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setFrom(null) + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x1"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + + // rlpEncoded is a rlp encoded EthereumAccessList transaction containing signatureData defined above. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumAccessList.combineSignedRawTransactions(rlpList); + } + + @Test + public void throwException_differentField() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Transactions containing different information cannot be combined."); + + String gas = "0x1e241"; + String gasPrice = "0x0a"; + String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + String chainID = "0x1"; + String input = "0x616263646566"; + String value = "0x0"; + String nonce = "0x4"; + AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + // rlpEncoded is a rlp encoded string of EthereumAccessList transaction containing above fields. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + // All fields are same with above rlpEncoded EthereumAccessList transaction except accessList field. + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setAccessList(new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + ))) + .setTo(to) + ); + + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumAccessList.combineSignedRawTransactions(rlpList); + } + } + + public static class appendSignaturesTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String to = "0x8723590d5D60e35f7cE0Db5C09D3938b26fF80Ae"; + static BigInteger value = BigInteger.ONE; + static BigInteger gas = BigInteger.valueOf(90000); + static String gasPrice = "0x19"; + static String nonce = "0x3a"; + static BigInteger chainID = BigInteger.valueOf(2019); + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + )) + ); + + static SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x1"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + ); + + @Test + public void appendSignature() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + ); + + ethereumAccessList.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void throwException_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + ); + + ethereumAccessList.appendSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )); + } + + + @Test + public void throwException_setSignature_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + } + + + @Test + public void appendSignatureWithEmptySig() { + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumAccessList.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void appendSignatureList() { + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList) + .setTo(to) + ); + + ethereumAccessList.appendSignatures(list); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void appendSignatureList_EmptySig() { + List list = new ArrayList<>(); + list.add(signatureData); + + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumAccessList.appendSignatures(list); + + assertEquals(signatureData, ethereumAccessList.getSignatures().get(0)); + } + + @Test + public void throwException_appendData_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumAccessList.appendSignatures(signatureData); + } + + @Test + public void throwException_appendList_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .setSignatures(list) + ); + + ethereumAccessList.appendSignatures(list); + } + + @Test + public void throwException_tooLongSignatures() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures are too long " + TransactionType.TxTypeEthereumAccessList.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + list.add(signatureData); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setTo(to) + .setAccessList(accessList) + .setValue(value) + ); + + ethereumAccessList.appendSignatures(list); + } + } + + public static class getRLPEncodingForSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x0"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761", + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + + @Test + public void getRLPEncodingForSignature() { + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + + String expected = "0x01f9011401040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878086616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7ca0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681"; + String rlpEncodedSign = ethereumAccessList.getRLPEncodingForSignature(); + + assertEquals(expected, rlpEncodedSign); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setGas(gas) + .setGasPrice(gasPrice) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + + String encoded = ethereumAccessList.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_GasPrice() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("gasPrice is undefined. Define gasPrice in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + String encoded = ethereumAccessList.getRLPEncodingForSignature(); + + } + + @Test + public void throwException_NotDefined_ChainID() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + ); + String encoded = ethereumAccessList.getRLPEncodingForSignature(); + } + } + + public static class signWithKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x1"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c", + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681" + )) + )); + + public static EthereumAccessList createEthereumAccessList() { + return caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setInput(input) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0xbf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7f", + "64bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8" + ); + static String expectedRlpEncoded = "0x7801f9015701040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d870186616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7ca0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a0bf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7fa064bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8"; + + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKey_Keyring() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring, 0, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoIndex() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoSigner() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring, 0); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_Only() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(coupledKeyring); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_KeyString_NoIndex() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("TxTypeEthereumAccessList cannot be signed with a decoupled keyring."); + + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + ethereumAccessList.sign(deCoupledKeyring); + } + + @Test + public void throwException_notEqualAddress() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The from address of the transaction is different with the address of the keyring to use"); + + EthereumAccessList ethereumAccessList = caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setNonce(nonce) + .setGas(Numeric.toBigInt(gas)) + .setGasPrice(Numeric.toBigInt(gasPrice)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setFrom("0x7b65b75d204abed71587c9e519a89277766aaaa1") + .setTo(to) + .setAccessList(accessList) + ); + + ethereumAccessList.sign(coupledKeyring); + } + } + + public static class signWithKeysTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String gas = "0x1e241"; + static String gasPrice = "0x0a"; + static String to = "0x095e7baea6a6c7c4c2dfeb977efac326af552d87"; + static String chainID = "0x1"; + static String input = "0x616263646566"; + static String value = "0x1"; + static String nonce = "0x4"; + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + )), + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0xd2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7c", + "0xd2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761" + )), + new AccessTuple("0x0000000000000000000000000000000000000003", + Arrays.asList( + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681" + )) + )); + + public static EthereumAccessList createEthereumAccessList() { + return caver.transaction.ethereumAccessList.create( + TxPropertyBuilder.ethereumAccessList() + .setTo(to) + .setNonce(nonce) + .setGas(gas) + .setGasPrice(gasPrice) + .setInput(input) + .setChainId(chainID) + .setValue(value) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0xbf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7f", + "64bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8" + ); + static String expectedRlpEncoded = "0x7801f9015701040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d870186616263646566f8eef7940000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000000f85994284e47e6130523b2507ba38cea17dd40a20a0cd0f842a0d2b691e13d4c3754fbe5dc75439f25dd11a908d89f5bbc55cd5bc4978f078b7ca0d2db659067b2b322f7010149472b81f172b9e331e1831ebee11c7b73facb0761f859940000000000000000000000000000000000000003f842a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f098368101a0bf84d5909e08e2e2bb1d5fa975fc2886fa0306c3279f1ad44ade0b8c5c094e7fa064bb96aea6a5b42fc0ef65365b7eb2b347de4e9a58167975307173b7bd52a4a8"; + + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKeys_KeyString() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + AbstractTransaction tx = ethereumAccessList.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKeys_KeyString_KlaytnWalletKeyFormat() throws IOException { + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.createFromPrivateKey(privateKey).getAddress(); + AbstractTransaction tx = ethereumAccessList.sign(klaytnKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumAccessList + " cannot be signed with a decoupled keyring."); + + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.generate().getAddress(); + ethereumAccessList.sign(klaytnKey); + } + + @Test + public void throwException_multipleKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumAccessList + " cannot be signed with a decoupled keyring."); + + String[] privateKeyArr = { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }; + + AbstractKeyring keyring = caver.wallet.keyring.createWithMultipleKey( + caver.wallet.keyring.generate().getAddress(), + privateKeyArr + ); + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + ethereumAccessList.sign(keyring); + } + + @Test + public void throwException_roleBasedKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumAccessList + " cannot be signed with a decoupled keyring."); + + String[][] privateKeyArr = { + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + } + }; + + EthereumAccessList ethereumAccessList = createEthereumAccessList(); + + AbstractKeyring keyring = caver.wallet.keyring.createWithRoleBasedKey( + caver.wallet.keyring.generate().getAddress(), + Arrays.asList(privateKeyArr) + ); + ethereumAccessList.sign(keyring); + } + } + + public static class recoverPublicKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void recoverPublicKey() { + String expectedPublicKey = "0x3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3"; + SignatureData signatureData = new SignatureData( + "0x1", + "0xbfc80a874c43b71b67c68fa5927d1443407f31aef4ec6369bbecdb76fc39b0c0", + "0x193e62c1dd63905aee7073958675dcb45d78c716a9a286b54a496e82cb762f26" + ); + + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + )) + ); + + EthereumAccessList tx = new EthereumAccessList.Builder() + .setFrom("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") + .setTo("0x7b65b75d204abed71587c9e519a89277766ee1d0") + .setValue("0xa") + .setChainId("0x2") + .setGasPrice("0x19") + .setNonce("0x4d2") + .setGas("0xf4240") + .setInput("0x31323334") + .setAccessList(accessList) + .setSignatures(signatureData) + .build(); + + List publicKeys = tx.recoverPublicKeys(); + assertEquals(expectedPublicKey, publicKeys.get(0)); + } + } + +} diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java new file mode 100644 index 000000000..b05b6191c --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/EthereumDynamicFeeTest.java @@ -0,0 +1,1664 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.common.transaction; + +import com.klaytn.caver.Caver; +import com.klaytn.caver.abi.datatypes.Array; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.TransactionHasher; +import com.klaytn.caver.transaction.TxPropertyBuilder; +import com.klaytn.caver.transaction.type.EthereumAccessList; +import com.klaytn.caver.transaction.type.EthereumDynamicFee; +import com.klaytn.caver.transaction.type.TransactionType; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import com.klaytn.caver.wallet.keyring.AbstractKeyring; +import com.klaytn.caver.wallet.keyring.SignatureData; +import org.junit.*; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.crypto.Hash; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(Enclosed.class) +public class EthereumDynamicFeeTest { + + public static class createInstance { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + String nonce = "0x4D2"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + String chainID = "0x2710"; + String input = "0x31323334"; + String value = "0x1"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void createInstance() throws IOException { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce("0x") + .setGas(gas) + .setMaxFeePerGas("0x") + .setMaxPriorityFeePerGas("0x") + .setChainId("0x") + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + + assertNotNull(ethereumDynamicFee); + + ethereumDynamicFee.fillTransaction(); + assertNotNull(ethereumDynamicFee.getNonce()); + assertNotNull(ethereumDynamicFee.getMaxPriorityFeePerGas()); + assertNotNull(ethereumDynamicFee.getMaxFeePerGas()); + assertNotNull(ethereumDynamicFee.getChainId()); + assertEquals(accessList, ethereumDynamicFee.getAccessList()); + assertEquals(TransactionType.TxTypeEthereumDynamicFee.toString(), ethereumDynamicFee.getType()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + String value = "invalid"; + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String to = "invalid"; + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setInput(input) + .setValue(value) + .setAccessList(accessList) + ); + } + } + + public static class createInstanceBuilder { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + String nonce = "0x4D2"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + String chainID = "0x2710"; + String input = "0x31323334"; + String value = "0x1"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + @Test + public void BuilderTest() { + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertNotNull(ethereumDynamicFee); + assertEquals(TransactionType.TxTypeEthereumDynamicFee.toString(), ethereumDynamicFee.getType()); + } + + @Test + public void BuilderTestWithBigInteger() { + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(Numeric.toBigInt(maxPriorityFeePerGas)) + .setMaxFeePerGas(Numeric.toBigInt(maxFeePerGas)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setTo(to) + .setAccessList(accessList) + .build(); + + assertEquals(gas, ethereumDynamicFee.getGas()); + assertEquals(maxPriorityFeePerGas, ethereumDynamicFee.getMaxPriorityFeePerGas()); + assertEquals(maxFeePerGas, ethereumDynamicFee.getMaxFeePerGas()); + assertEquals(chainID, ethereumDynamicFee.getChainId()); + assertEquals(value, ethereumDynamicFee.getValue()); + assertEquals(accessList, ethereumDynamicFee.getAccessList()); + } + + @Test + public void BuilderTestRPC() throws IOException { + String gas = "0x0f4240"; + String to = "7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0x0a"; + + Caver caver = new Caver(Caver.DEFAULT_URL); + AbstractKeyring keyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setKlaytnCall(caver.rpc.getKlay()) + .setGas(gas) + .setInput(input) + .setValue(value) + .setFrom(keyring.getAddress()) // For Test. It automatically filled when executed EthereumDynamicFee.signWithKey or signWithKeysTest. + .setTo(to) + .setAccessList(accessList) + .build(); + + ethereumDynamicFee.fillTransaction(); + assertNotNull(ethereumDynamicFee.getNonce()); + assertNotNull(ethereumDynamicFee.getMaxPriorityFeePerGas()); + assertNotNull(ethereumDynamicFee.getMaxFeePerGas()); + assertNotNull(ethereumDynamicFee.getChainId()); + assertNotNull(ethereumDynamicFee.getAccessList()); + } + + @Test + public void throwException_invalid_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("0x") + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_invalid_value2() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value"); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setTo(to) + .setValue("invalid") + .build(); + } + + @Test + public void throwException_invalid_To() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid address."); + + String gas = "0xf4240"; + String to = "invalid"; + String input = "0x31323334"; + String value = "0xa"; + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + + @Test + public void throwException_missingGas() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("gas is missing"); + + EthereumDynamicFee ethereumDynamicFee = new EthereumDynamicFee.Builder() + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setInput(input) + .setValue(value) + .setTo(to) + .setAccessList(accessList) + .build(); + } + } + + public static class getRLPEncodingAndDecodingTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + @Test + public void getRLPEncodingAndDecoding() { + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + // 0 y-parity + String expectedRLP = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce("0x25") + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo("0x1fc92c23f71a7de4cdb4394a37fc636986a0f484") + .setValue(value) + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(accessList) + .setSignatures(signatureData) + ); + assertEquals(expectedRLP, ethereumDynamicFee.getRLPEncoding()); + // decoding + EthereumDynamicFee decodedEthereumDynamicFee = (EthereumDynamicFee) caver.transaction.decode(expectedRLP); + assertTrue(ethereumDynamicFee.compareTxField(decodedEthereumDynamicFee, true)); + + // 1 y-parity + expectedRLP = "0x7802f9010f822710288505d21dba008505d21dba00829c40947988508e9236a5b796ddbb6ac40864777a414f5f01b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf85994d9d6bd9e2186233d9441bde052504b926f2e0bb2f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a054d6ea6f359a7d3546199ac93dca216918b45647a45b6f32be58f33735a696b7a07179ffc15f5c6b4b08efc4f7306548c435529edc2e5b8243d1193f52085dbc65"; + ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce("0x28") + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo("0x7988508e9236a5b796ddbb6ac40864777a414f5f") + .setValue(value) + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(caver.transaction.utils.accessList.create( + Arrays.asList( + caver.transaction.utils.accessTuple.create( + "0xd9d6bd9e2186233d9441bde052504b926f2e0bb2", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + ) + ) + )) + .setSignatures(new SignatureData( + "0x01", + "0x54d6ea6f359a7d3546199ac93dca216918b45647a45b6f32be58f33735a696b7", + "0x7179ffc15f5c6b4b08efc4f7306548c435529edc2e5b8243d1193f52085dbc65") + ) + ); + assertEquals(expectedRLP, ethereumDynamicFee.getRLPEncoding()); + // decoding + decodedEthereumDynamicFee = (EthereumDynamicFee) caver.transaction.decode(expectedRLP); + assertTrue(ethereumDynamicFee.compareTxField(decodedEthereumDynamicFee, true)); + } + + @Test + public void throwException_NoNonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x1"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + @Test + public void throwException_NoMaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + @Test + public void throwException_NoMaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String chainID = "0x1"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + @Test + public void throwException_NoChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + String nonce = "0x4D2"; + String gas = "0xf4240"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String to = "0x7b65b75d204abed71587c9e519a89277766ee1d0"; + String input = "0x31323334"; + String value = "0xa"; + + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0xb2a5a15550ec298dc7dddde3774429ed75f864c82caeb5ee24399649ad731be9"), + Numeric.hexStringToByteArray("0x29da1014d16f2011b3307f7bbe1035b6e699a4204fc416c763def6cefd976567") + ); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setValue(value) + .setInput(input) + .setSignatures(list) + .setTo(to) + .setAccessList(null) + ); + + ethereumDynamicFee.getRLPEncoding(); + } + + } + + public static class getTransactionHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + @Test + public void getTransactionHash() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + String expected = "0xf3a22f2ce973c9aa250d0dc64044e3dec9592b787f5abe3f557f3e81b99e3ca1"; + String txHash = ethereumDynamicFee.getTransactionHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumDynamicFee.getTransactionHash(); + } + + @Test + public void throwException_NotDefined_MaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getRawTransaction(); + } + + @Test + public void throwException_NotDefined_MaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getRawTransaction(); + } + + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getTransactionHash(); + } + } + + + public static class getSenderTxHashTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + @Test + public void getSenderTxHash() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + String expected = "0xf3a22f2ce973c9aa250d0dc64044e3dec9592b787f5abe3f557f3e81b99e3ca1"; + String txHash = ethereumDynamicFee.getSenderTxHash(); + + assertEquals(expected, txHash); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumDynamicFee.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_MaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getSenderTxHash(); + } + + @Test + public void throwException_NotDefined_MaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setNonce(nonce) + .setChainId(chainID) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getSenderTxHash(); + } + + + @Test + public void throwException_NotDefined_ChainId() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setNonce(nonce) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setValue(value) + .setInput(input) + .setTo(to) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + ethereumDynamicFee.getSenderTxHash(); + } + } + + public static class combineSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + @Test + public void combineSignature() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + // rlpEncoded is a rlp encoded EthereumDynamicFee transaction containing signatureData defined above. + String rlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumDynamicFee.combineSignedRawTransactions(rlpList); + assertEquals(rlpEncoded, combined); + EthereumDynamicFee decoded = (EthereumDynamicFee) caver.transaction.decode(combined); + Assert.assertTrue(decoded.getSignatures().equals(Arrays.asList(signatureData))); + } + + @Test + public void combineSignature_EmptySig() { + SignatureData emptySig = SignatureData.getEmptySignature(); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(emptySig) + ); + + // rlpEncoded is a rlp encoded EthereumDynamicFee transaction containing signatureData defined above. + String rlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + String combined = ethereumDynamicFee.combineSignedRawTransactions(rlpList); + + assertEquals(rlpEncoded, combined); + EthereumDynamicFee decoded = (EthereumDynamicFee) caver.transaction.decode(combined); + Assert.assertTrue(decoded.getSignatures().equals(Arrays.asList(signatureData))); + } + + @Test + public void throwException_existSignature() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x01"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + + // rlpEncoded is a rlp encoded EthereumDynamicFee transaction containing signatureData defined above. + String rlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000780a04fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040a07d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc"; + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumDynamicFee.combineSignedRawTransactions(rlpList); + } + + @Test + public void throwException_differentField() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Transactions containing different information cannot be combined."); + + // rlpEncoded is a rlp encoded string of EthereumDynamicFee transaction containing above fields. + String rlpEncoded = "0x7801f8df01040a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc01a019676433856a1bd3650e22c210e20c7efdedf1d4f555f1ab6eb7845024f52d99a070feddce085399eb085b55254fbc8bb5bf912464316b20c3be39bca9015da235"; + + // All fields are same with above rlpEncoded EthereumDynamicFee transaction except maxFeePerGas field. + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas("0x1") + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + + List rlpList = new ArrayList<>(); + rlpList.add(rlpEncoded); + + ethereumDynamicFee.combineSignedRawTransactions(rlpList); + } + } + + public static class appendSignaturesTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + SignatureData signatureData = new SignatureData( + Numeric.hexStringToByteArray("0x0"), + Numeric.hexStringToByteArray("0x4fc52da183020a27dc4b684a45404445630e946b0c1a37edeb538d4bdae63040"), + Numeric.hexStringToByteArray("0x7d56dbcc61f42ffcbced105f838d20b8fe71e85a4d0344c7f60815fddfeae4cc") + ); + + @Test + public void appendSignature() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void throwException_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )); + } + + + @Test + public void throwException_setSignature_invalidParity() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid signature: The y-parity of the transaction should either be 0 or 1."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(new SignatureData( + Numeric.hexStringToByteArray("0x25"), + Numeric.hexStringToByteArray("0xade9480f584fe481bf070ab758ecc010afa15debc33e1bd75af637d834073a6e"), + Numeric.hexStringToByteArray("0x38160105d78cef4529d765941ad6637d8dcf6bd99310e165fee1c39fff2aa27e") + )) + ); + } + + + @Test + public void appendSignatureWithEmptySig() { + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumDynamicFee.appendSignatures(signatureData); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void appendSignatureList() { + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(list); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void appendSignatureList_EmptySig() { + List list = new ArrayList<>(); + list.add(signatureData); + + SignatureData emptySignature = SignatureData.getEmptySignature(); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(emptySignature) + ); + + ethereumDynamicFee.appendSignatures(list); + + assertEquals(signatureData, ethereumDynamicFee.getSignatures().get(0)); + } + + @Test + public void throwException_appendData_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(signatureData) + ); + + ethereumDynamicFee.appendSignatures(signatureData); + } + + @Test + public void throwException_appendList_existsSignatureInTransaction() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures already defined." + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + .setSignatures(list) + ); + + ethereumDynamicFee.appendSignatures(list); + } + + @Test + public void throwException_tooLongSignatures() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Signatures are too long " + TransactionType.TxTypeEthereumDynamicFee.toString() + " cannot include more than one signature."); + + List list = new ArrayList<>(); + list.add(signatureData); + list.add(signatureData); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + ethereumDynamicFee.appendSignatures(list); + } + } + + public static class getRLPEncodingForSignatureTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(Caver.DEFAULT_URL); + String nonce = "0x25"; + String gas = "0x9c40"; + String maxPriorityFeePerGas = "0x5d21dba00"; + String maxFeePerGas = "0x5d21dba00"; + String chainID = "0x2710"; + String value = "0x1"; + String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + @Test + public void getRLPEncodingForSignature() { + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + String expected = "0x02f8cc822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007"; + String rlpEncodedSign = ethereumDynamicFee.getRLPEncodingForSignature(); + + assertEquals(expected, rlpEncodedSign); + } + + @Test + public void throwException_NotDefined_Nonce() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("nonce is undefined. Define nonce in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_MaxPriorityFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxPriorityFeePerGas is undefined. Define maxPriorityFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_MaxFeePerGas() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("maxFeePerGas is undefined. Define maxFeePerGas in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + + @Test + public void throwException_NotDefined_ChainID() { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("chainId is undefined. Define chainId in transaction or use 'transaction.fillTransaction' to fill values."); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + String encoded = ethereumDynamicFee.getRLPEncodingForSignature(); + } + } + + public static class signWithKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String nonce = "0x25"; + static String gas = "0x9c40"; + static String maxPriorityFeePerGas = "0x5d21dba00"; + static String maxFeePerGas = "0x5d21dba00"; + static String chainID = "0x2710"; + static String value = "0x1"; + static String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + static String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + static AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + public static EthereumDynamicFee createEthereumDynamicFee() { + return caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0x2e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208f", + "0x76cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4" + ); + static String expectedRlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a02e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208fa076cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4"; + + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKey_Keyring() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring, 0, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoIndex() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_NoSigner() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring, 0); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_Keyring_Only() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(coupledKeyring); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKey_KeyString_NoIndex() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("TxTypeEthereumDynamicFee cannot be signed with a decoupled keyring."); + + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + ethereumDynamicFee.sign(deCoupledKeyring); + } + + @Test + public void throwException_notEqualAddress() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The from address of the transaction is different with the address of the keyring to use"); + + EthereumDynamicFee ethereumDynamicFee = caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(Numeric.toBigInt(gas)) + .setMaxPriorityFeePerGas(Numeric.toBigInt(maxPriorityFeePerGas)) + .setMaxFeePerGas(Numeric.toBigInt(maxFeePerGas)) + .setChainId(Numeric.toBigInt(chainID)) + .setInput(input) + .setValue(Numeric.toBigInt(value)) + .setFrom("0x7b65b75d204abed71587c9e519a89277766aaaa1") + .setTo(to) + .setAccessList(accessList) + ); + + ethereumDynamicFee.sign(coupledKeyring); + } + } + + public static class signWithKeysTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + + static Caver caver = new Caver(); + + static AbstractKeyring coupledKeyring; + static AbstractKeyring deCoupledKeyring; + static String privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + + static String nonce = "0x25"; + static String gas = "0x9c40"; + static String maxPriorityFeePerGas = "0x5d21dba00"; + static String maxFeePerGas = "0x5d21dba00"; + static String chainID = "0x2710"; + static String value = "0x1"; + static String input = "0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039"; + static String to = "0x1fc92c23f71a7de4cdb4394a37fc636986a0f484"; + static AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + public static EthereumDynamicFee createEthereumDynamicFee() { + return caver.transaction.ethereumDynamicFee.create( + TxPropertyBuilder.ethereumDynamicFee() + .setNonce(nonce) + .setGas(gas) + .setMaxPriorityFeePerGas(maxPriorityFeePerGas) + .setMaxFeePerGas(maxFeePerGas) + .setChainId(chainID) + .setTo(to) + .setValue(value) + .setInput(input) + .setAccessList(accessList) + ); + } + + static SignatureData expectedSignature = new SignatureData( + "0x01", + "0x2e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208f", + "0x76cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4" + ); + static String expectedRlpEncoded = "0x7802f9010f822710258505d21dba008505d21dba00829c40941fc92c23f71a7de4cdb4394a37fc636986a0f48401b844a9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039f85bf8599467116062f1626f7b3019631f03d301b8f701f709f842a00000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000701a02e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208fa076cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4"; + + @BeforeClass + public static void preSetup() { + coupledKeyring = caver.wallet.keyring.createFromPrivateKey(privateKey); + deCoupledKeyring = caver.wallet.keyring.createWithSingleKey( + caver.wallet.keyring.generate().getAddress(), + privateKey + ); + } + + @Test + public void signWithKeys_KeyString() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + AbstractTransaction tx = ethereumDynamicFee.sign(privateKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void signWithKeys_KeyString_KlaytnWalletKeyFormat() throws IOException { + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.createFromPrivateKey(privateKey).getAddress(); + AbstractTransaction tx = ethereumDynamicFee.sign(klaytnKey, TransactionHasher::getHashForSignature); + Assert.assertEquals(expectedSignature, tx.getSignatures().get(0)); + Assert.assertEquals(expectedRlpEncoded, tx.getRawTransaction()); + } + + @Test + public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumDynamicFee + " cannot be signed with a decoupled keyring."); + + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + String klaytnKey = privateKey + "0x00" + caver.wallet.keyring.generate().getAddress(); + ethereumDynamicFee.sign(klaytnKey); + } + + @Test + public void throwException_multipleKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumDynamicFee + " cannot be signed with a decoupled keyring."); + + String[] privateKeyArr = { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }; + + AbstractKeyring keyring = caver.wallet.keyring.createWithMultipleKey( + caver.wallet.keyring.generate().getAddress(), + privateKeyArr + ); + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + ethereumDynamicFee.sign(keyring); + } + + @Test + public void throwException_roleBasedKeyring() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(TransactionType.TxTypeEthereumDynamicFee + " cannot be signed with a decoupled keyring."); + + String[][] privateKeyArr = { + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + }, + { + caver.wallet.keyring.generateSingleKey(), + caver.wallet.keyring.generateSingleKey() + } + }; + + EthereumDynamicFee ethereumDynamicFee = createEthereumDynamicFee(); + + AbstractKeyring keyring = caver.wallet.keyring.createWithRoleBasedKey( + caver.wallet.keyring.generate().getAddress(), + Arrays.asList(privateKeyArr) + ); + ethereumDynamicFee.sign(keyring); + } + } + + public static class recoverPublicKeyTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void recoverPublicKey() { + + String expectedPublicKey = "0x3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3"; + SignatureData signatureData = new SignatureData( + "0x01", + "0x2e07db45b088d6a2cabce6c250b94252dd505789a3912e4fe08a2566973b208f", + "0x76cbcc2f02063ee6c79ffea7f2078267405c3819e8ac4db0c504223d55892ba4" + ); + + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x67116062f1626f7b3019631f03d301b8f701f709", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ) + )) + ); + + EthereumDynamicFee tx = new EthereumDynamicFee.Builder() + .setFrom("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") + .setTo("0x1fc92c23f71a7de4cdb4394a37fc636986a0f484") + .setValue("0x1") + .setChainId("0x2710") + .setMaxPriorityFeePerGas("0x5d21dba00") + .setMaxFeePerGas("0x5d21dba00") + .setNonce("0x25") + .setGas("0x9c40") + .setInput("0xa9059cbb0000000000000000000000008a4c9c443bb0645df646a2d5bb55def0ed1e885a0000000000000000000000000000000000000000000000000000000000003039") + .setAccessList(accessList) + .setSignatures(signatureData) + .build(); + + List publicKeys = tx.recoverPublicKeys(); + assertEquals(expectedPublicKey, publicKeys.get(0)); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java index 2d4ecf1e7..ca67cf18c 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/LegacyTransactionTest.java @@ -369,7 +369,7 @@ public void signWithKey_KeyString_NoIndex() throws IOException { @Test public void throwException_decoupledKey() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); LegacyTransaction legacyTransaction = createLegacyTransaction(); legacyTransaction.sign(deCoupledKeyring); @@ -483,7 +483,7 @@ public void signWithKeys_KeyString_NoSigner() throws IOException { @Test public void throwException_decoupledKey() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); LegacyTransaction legacyTransaction = createLegacyTransaction(); legacyTransaction.sign(deCoupledKeyring); @@ -492,7 +492,7 @@ public void throwException_decoupledKey() throws IOException { @Test public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); LegacyTransaction legacyTransaction = createLegacyTransaction(); @@ -503,7 +503,7 @@ public void throwException_KlaytnWalletKeyFormat_decoupledKey() throws IOExcepti @Test public void throwException_multipleKeyring() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); String[] privateKeyArr = { caver.wallet.keyring.generateSingleKey(), @@ -522,7 +522,7 @@ public void throwException_multipleKeyring() throws IOException { @Test public void throwException_roleBasedKeyring() throws IOException { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A legacy transaction cannot be signed with a decoupled keyring."); + expectedException.expectMessage("TxTypeLegacyTransaction cannot be signed with a decoupled keyring."); String[][] privateKeyArr = { { diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java index 59ddb45da..9139adf0c 100644 --- a/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java +++ b/core/src/test/java/com/klaytn/caver/common/transaction/TransactionHelperTest.java @@ -62,7 +62,6 @@ public void checkTxObject(Transaction.TransactionData expected, AbstractTransact assertEquals(expected.getFrom(), actual.getFrom()); assertEquals(expected.getGas(), actual.getGas()); assertEquals(expected.getNonce(), actual.getNonce()); - assertEquals(expected.getGasPrice(), actual.getGasPrice()); assertEquals(expected.getHash(), actual.getTransactionHash()); } @@ -83,6 +82,13 @@ public void legacyTransaction() { checkTxObject(txObject, legacyTransaction); } + @Test + public void ethereumAccessList() { + Transaction.TransactionData txObject = sampleTxData.get("ethereumAccessList"); + EthereumAccessList ethereumAccessList = (EthereumAccessList)txObject.convertToCaverTransaction(caver.rpc.klay); + checkTxObject(txObject, ethereumAccessList); + } + @Test public void valueTransfer() { Transaction.TransactionData txObject = sampleTxData.get("valueTransfer"); diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java new file mode 100644 index 000000000..c07593f0f --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessListTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.common.transaction.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.web3j.utils.Numeric; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Arrays; + +@RunWith(Enclosed.class) +public class AccessListTest { + + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + static Caver caver = new Caver(); + + static AccessList accessList = new AccessList(Arrays.asList( + new AccessTuple("0x284e47e6130523b2507ba38cea17dd40a20a0cd0", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x6eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681", + "0x46d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fc" + )) + )); + static String encodedAccessList = "0xf87cf87a94284e47e6130523b2507ba38cea17dd40a20a0cd0f863a00000000000000000000000000000000000000000000000000000000000000000a046d62a62fb985e2e7691a9044b8fae9149311c7f3dcf669265fe5c96072ba4fca06eab5ba2ea17e1ef4eac404d25f1fe9224421e3b639aec73d3b99c39f0983681"; + + public static class createInstanceTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create() throws IOException { + AccessList accessList = caver.transaction.utils.accessList.create( + Arrays.asList( + new AccessTuple( + "0x2c8ad0ea2e0781db8b8c9242e07de3a5beabb71a", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + ) + ) + ); + } + } + + public static class deserializeTest { + + @Test + public void oneAddressAndOneStorageKey() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + @Test + public void oneAddressAndManyStorageKeys() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + @Test + public void manyAccessList() throws IOException { + String accessListJson = "[\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\",\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"address\": \"0xca7a99380131e6c76cfa622396347107aeedca2d\",\n" + + " \"storageKeys\": [\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\",\n" + + " \"0x496cb27939a5289c1664367767cdead9748c2dbce7f1a886fd72a2e65f233a48\",\n" + + " \"0x112645f3af43346fcb71a5d46c30de31408b7a1458cf4bf5f87b587226dec6f1\",\n" + + " \"0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f\"\n" + + " ]\n" + + " }\n" + + "]"; + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(accessListJson); + AccessList accessList = objectMapper.readValue(reader, AccessList.class); + System.out.println(objectToString(accessList)); + } + + } + + public static class decodeTest { + @Test + public void decodeString() throws IOException { + AccessList decodedAccessList = caver.transaction.utils.accessList.decode(encodedAccessList); + System.out.println(objectToString(decodedAccessList)); + Assert.assertEquals(accessList, decodedAccessList); + } + } + + public static class encodeTest { + @Test + public void encodeToBytes() { + byte[] encoded = accessList.encodeToBytes(); + String encodedString = Numeric.toHexString(encoded); + + AccessList.decode(encodedString); + Assert.assertEquals(encodedAccessList, encodedString); + } + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java new file mode 100644 index 000000000..a466885cc --- /dev/null +++ b/core/src/test/java/com/klaytn/caver/common/transaction/utils/AccessTupleTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2022 The caver-java Authors + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klaytn.caver.common.transaction.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.klaytn.caver.Caver; +import com.klaytn.caver.transaction.utils.AccessTuple; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Arrays; + +@RunWith(Enclosed.class) +public class AccessTupleTest { + + public static String objectToString(Object value) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + return ow.writeValueAsString(value); + } + + static Caver caver = new Caver(); + + public static class createInstanceTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create() throws IOException { + AccessTuple expectedAccessTuple = caver.transaction.utils.accessTuple.create( + "0x4173c51bd0e64a4c58656a5401373a476e8534ec", + Arrays.asList( + "0x4f42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717" + ) + ); + + AccessTuple accessTuple = caver.transaction.utils.accessTuple.create( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + + // Checksum address should be accepted. + // Uppercase storage keys will be converted as small case letter when creating AccessTuple. + Assert.assertEquals(expectedAccessTuple, accessTuple); + Assert.assertTrue(Arrays.equals(expectedAccessTuple.encodeToBytes(), accessTuple.encodeToBytes())); + } + + @Test + public void create_invalidAddress() { + // 1 byte-short + String invalidAddress = "0x4173c51bd0e64a4c58656a5401373a476e8534"; + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid address. Address: " + invalidAddress); + + caver.transaction.utils.accessTuple.create( + invalidAddress, + Arrays.asList( + "0X4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + } + + @Test + public void create_invalidStorageKey() { + // 1 byte-short + String invalidStorageKey = "0x4F42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B"; + expectedException.expect(RuntimeException.class); + expectedException.expectMessage("Invalid storageKey. Storage key should be a 32 bytes of hex string " + invalidStorageKey.toLowerCase()); + + caver.transaction.utils.accessTuple.create( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + invalidStorageKey, + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717" + ) + ); + } + + @Test + public void create_orderedKeys() { + AccessTuple accessTuple = caver.transaction.utils.accessTuple.create( + "0x4173C51bd0e64A4c58656A5401373A476E8534Ec", + Arrays.asList( + "0XC4A32ABDF1905059FDFC304AAE1E8924279A36B2A6428552237F590156ED7717", + "0XaF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XbF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98", + "0XcF42391603E79B2A90C3FBFC070C995EB1163E0AC00FB4E8F3DA2DC81C451B98" + ) + ); + Assert.assertEquals( + Arrays.asList( + "0xaf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xbf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98", + "0xc4a32abdf1905059fdfc304aae1e8924279a36b2a6428552237f590156ed7717", + "0xcf42391603e79b2a90c3fbfc070c995eb1163e0ac00fb4e8f3da2dc81c451b98" + ), + accessTuple.getStorageKeys() + ); + } + + } +} diff --git a/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java b/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java index 9d3e9acff..738f06b6d 100644 --- a/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java +++ b/core/src/test/java/com/klaytn/caver/common/utils/UtilsTest.java @@ -1161,6 +1161,30 @@ public void alreadyPrefix() throws SignatureException { checkAddress(keyring.getAddress(), actualAddress); } + + @Test + public void withMessageAndSignatureIfVis0() throws SignatureException { + String expectedSignedMessage = "0xc69018da9396c4b87947e0784625af7475caf46e2af9cf57a44673ff0f625258642d8993751ae67271bcc131aa065adccf9f16fc4953f9c48f4a80d675c09ae800"; + String expectedAddress = "0xb6a1f97502431e6f8d701f9e192c3cc43c07351a"; + String message = "Klaytn Test"; + + SignatureData signatureData = caver.utils.decodeSignature(expectedSignedMessage); + String actualAddress = caver.utils.recover(message, signatureData); + + checkAddress(expectedAddress, actualAddress); + } + + @Test + public void withMessageAndSignatureIfVis1() throws SignatureException { + String expectedSignedMessage = "0x90dda6bb189ccb6cbda754c3696b91d28ca2486d7f2905ac90e1a831a2bb65cd4c55a54d6365ea5cb0f33330ed82b687d7b808137a0a7cdc5b620bb81d69d62701"; + String expectedAddress = "0xa05b2b058dbc5926969cb7f33cff6d8747472022"; + String message = "Some data"; + + SignatureData signatureData = caver.utils.decodeSignature(expectedSignedMessage); + String actualAddress = caver.utils.recover(message, signatureData); + + checkAddress(expectedAddress, actualAddress); + } } public static class decodeSignatureTest { @@ -1237,6 +1261,37 @@ public void recoverWithSampleData() throws SignatureException { String actual = caver.utils.recoverPublicKey(message, signatureData); assertEquals(expected, actual); } + + @Test + public void recoverWithSampleDataIfVis0() throws SignatureException { + String expected = "0xb5df4d5e6b4ee7a136460b911a69030fdd42c18ed067bcc2e25eda1b851314fad994c5fe946aad01ca2e348d4ff3094960661a8bc095f358538af54aeea48ff3"; + + String message = "Some Message"; + SignatureData signatureData = new SignatureData( + "0x00", + "0x8213e560e7bbe1f2e28fd69cbbb41c9108b84c98cd7c2c88d3c8e3549fd6ab10", + "0x3ca40c9e20c1525348d734a6724db152b9244bff6e0ff0c2b811d61d8f874f00" + ); + + String actual = caver.utils.recoverPublicKey(message, signatureData); + assertEquals(expected, actual); + } + + @Test + public void recoverWithSampleDataIfVis1() throws SignatureException { + String expected = "0xd907f61c1270ed9601b20a53d55244fea002afca955e1a47a309c507ced26e8721d32f419f789d6216ca35bd47aa7adb2432cfc6d52dd74b552a1b749181c0ac"; + String address = caver.utils.publicKeyToAddress(expected); + + String message = "Some data"; + SignatureData signatureData = new SignatureData( + "0x01", + "0x90dda6bb189ccb6cbda754c3696b91d28ca2486d7f2905ac90e1a831a2bb65cd", + "0x4c55a54d6365ea5cb0f33330ed82b687d7b808137a0a7cdc5b620bb81d69d627" + ); + + String actual = caver.utils.recoverPublicKey(message, signatureData); + assertEquals(expected, actual); + } } public static class publicKeyToAddress { diff --git a/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java b/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java index 45f399431..9fef507be 100644 --- a/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java +++ b/core/src/test/java/com/klaytn/caver/common/wallet/KeyringTest.java @@ -885,9 +885,9 @@ public void multipleKey() { assertEquals(3, actualList.size()); for(int i=0; i expected, List actual) { + assertEquals(expected.size(), actual.size()); + + for(int i=0; i signatureDataList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + + assertEquals(1, signatureDataList.size()); + assertNotNull(signatureDataList.get(0).getR()); + assertNotNull(signatureDataList.get(0).getS()); + assertNotNull(signatureDataList.get(0).getV()); + assertTrue(Numeric.toBigInt(signatureDataList.get(0).getV()).compareTo(BigInteger.ONE) <= 0); + } + + @Test + public void coupleKey_With_NotExistedRole() { + AbstractKeyring keyring = KeyringFactory.generate(); + List expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(1, actualList.size()); + assertNotNull(actualList.get(0).getR()); + assertNotNull(actualList.get(0).getS()); + assertNotNull(actualList.get(0).getV()); + + checkSignature(expectedList, actualList); + } + + @Test + public void deCoupleKey() { + String address = PrivateKey.generate().getDerivedAddress(); + String privateKey = PrivateKey.generate().getPrivateKey(); + AbstractKeyring keyring = KeyringFactory.create(address, privateKey); + + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + assertEquals(1, actualList.size()); + assertNotNull(actualList.get(0).getR()); + assertNotNull(actualList.get(0).getS()); + assertNotNull(actualList.get(0).getV()); + assertTrue(Numeric.toBigInt(actualList.get(0).getV()).compareTo(BigInteger.ONE) <= 0); + } + + @Test + public void deCoupleKey_With_NotExistedRole() { + String address = PrivateKey.generate().getDerivedAddress(); + String privateKey = PrivateKey.generate().getPrivateKey(); + AbstractKeyring keyring = KeyringFactory.create(address, privateKey); + + List expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(1, actualList.size()); + checkSignature(expectedList, actualList); + } + + @Test + public void multipleKey() { + AbstractKeyring keyring = generateMultipleKeyring(3); + + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + assertEquals(3, actualList.size()); + + for(int i=0; i expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(3, actualList.size()); + checkSignature(expectedList, actualList); + } + + @Test + public void roleBasedKey() { + AbstractKeyring keyring = generateRoleBaseKeyring(new int[]{3,3,4}); + + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + assertEquals(3, actualList.size()); + + for(int i=0; i expectedList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.TRANSACTION.getIndex()); + List actualList = keyring.ecsign(HASH, AccountKeyRoleBased.RoleGroup.ACCOUNT_UPDATE.getIndex()); + + assertEquals(3, actualList.size()); + checkSignature(expectedList, actualList); + } + } } diff --git a/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java b/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java index 1dd8f753b..42ba3bdd4 100644 --- a/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java +++ b/core/src/test/java/com/klaytn/caver/legacy/feature/RpcTest.java @@ -337,7 +337,7 @@ public void testEstimateGas() throws Exception { ); Quantity response = caver.klay().estimateGas(callObject).send(); String result = response.getResult(); - assertEquals("0x5318", result); + assertNotNull(result); } @Test @@ -353,7 +353,7 @@ public void testEstimateComputationCost() throws Exception { ); Quantity response = caver.klay().estimateComputationCost(callObject, DefaultBlockParameterName.LATEST).send(); String result = response.getResult(); - assertEquals("0x5318", result); + assertNotNull(result); } @Test diff --git a/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java b/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java index 8e4c559fe..65fcc9c79 100644 --- a/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java +++ b/core/src/test/java/com/klaytn/caver/validator/ValidatorTest.java @@ -22,10 +22,10 @@ import com.klaytn.caver.account.AccountKeyLegacy; import com.klaytn.caver.methods.response.AccountKey; import com.klaytn.caver.rpc.Klay; -import com.klaytn.caver.transaction.type.AccountUpdate; -import com.klaytn.caver.transaction.type.FeeDelegatedValueTransfer; -import com.klaytn.caver.transaction.type.LegacyTransaction; -import com.klaytn.caver.transaction.type.ValueTransfer; +import com.klaytn.caver.transaction.AbstractTransaction; +import com.klaytn.caver.transaction.type.*; +import com.klaytn.caver.transaction.utils.AccessList; +import com.klaytn.caver.transaction.utils.AccessTuple; import com.klaytn.caver.wallet.keyring.KeyringFactory; import com.klaytn.caver.wallet.keyring.SignatureData; import com.klaytn.caver.wallet.keyring.SingleKeyring; @@ -1570,6 +1570,7 @@ public void withInvalidFeePayerSignature() throws IOException { public static class validateTransaction { static ValueTransfer.Builder txBuilder; static FeeDelegatedValueTransfer.Builder fdTxBuilder; + static EthereumAccessList.Builder accessTxBuilder; static AccountKey accountKey; @BeforeClass @@ -1634,6 +1635,33 @@ public static void init() { "0x4fe2a845d92ff48abca3e1d59637fab5f4a4e3172d91772d9bfce60760edc506" ) )); + + AccessList accessList = new AccessList( + Arrays.asList( + new AccessTuple( + "0x0000000000000000000000000000000000000001", + Arrays.asList( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + )) + ); + + accessTxBuilder = new EthereumAccessList.Builder() + .setFrom("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") + .setTo("0x7b65b75d204abed71587c9e519a89277766ee1d0") + .setValue("0xa") + .setChainId("0x2") + .setGasPrice("0x19") + .setNonce("0x4d2") + .setGas("0xf4240") + .setInput("0x31323334") + .setAccessList(accessList) + .setSignatures(new SignatureData( + "0x1", + "0xbfc80a874c43b71b67c68fa5927d1443407f31aef4ec6369bbecdb76fc39b0c0", + "0x193e62c1dd63905aee7073958675dcb45d78c716a9a286b54a496e82cb762f26" + )); + } @Test @@ -1645,8 +1673,11 @@ public void isCallValidateSender() throws IOException { Validator spyValidator = Mockito.spy(validator); spyValidator.validateTransaction(txBuilder.build()); - Mockito.verify(spyValidator).validateSender(any()); + + Validator spyValidator1 = Mockito.spy(validator); + spyValidator1.validateTransaction(accessTxBuilder.build()); + Mockito.verify(spyValidator1).validateSender(any()); } @Test @@ -1671,6 +1702,7 @@ public void validateTransaction() throws IOException { Validator validator = new Validator(klay); assertTrue(validator.validateTransaction(txBuilder.build())); assertTrue(validator.validateTransaction(fdTxBuilder.build())); + assertTrue(validator.validateTransaction(accessTxBuilder.build())); } } } \ No newline at end of file diff --git a/core/src/test/resources/TransactionSample.json b/core/src/test/resources/TransactionSample.json index c16032c5f..fa084bd9c 100644 --- a/core/src/test/resources/TransactionSample.json +++ b/core/src/test/resources/TransactionSample.json @@ -616,5 +616,79 @@ "transactionIndex":"0x0", "type":"TxTypeFeeDelegatedChainDataAnchoringWithRatio", "typeInt":74 + }, + "ethereumAccessList":{ + "accessList": [ + { + "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "storageKeys": [ + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f", + "0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1" + ] + }, + { + "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "storageKeys": [ + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f", + "0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1" + ] + } + ], + "blockHash": "0x62c20ccb0243406c85d22fabc83f7cc43c7f5a3e954af4fd70a4e87c38f8b24f", + "blockNumber": "0xb01a", + "chainID": "0x7e3", + "from": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "gas": "0x1869f", + "gasPrice": "0x5d21dba00", + "hash": "0x8c2972b9a5a63a906158f82ea89cde83ffe85ffca62c9b1b976fd0509e8b6674", + "input": "0x", + "nonce": "0x2", + "senderTxHash": "0x8c2972b9a5a63a906158f82ea89cde83ffe85ffca62c9b1b976fd0509e8b6674", + "signatures": [ + { + "V": "0x0", + "R": "0x49474db0a0b14b71c54ec00da7575d8c76045ec5bc6873d499c37015f73bfd30", + "S": "0x17fa9232414eb09a6dd1822e0fb4c77acb58bd524f0763863d99ea9c089761e6" + } + ], + "to": "0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499", + "transactionIndex": "0x0", + "type": "TxTypeEthereumAccessList", + "typeInt": 30721, + "value": "0x1" + }, + "ethereumDynamicFee": { + "accessList": [ + { + "address": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "storageKeys": [ + "0x0709c257577296fac29c739dad24e55b70a260497283cf9885ab67b4daa9b67f", + "0x3d228ec053cf862382d404e79c80391ade4af5aca19ace983a4c6bd698a4e9f1" + ] + } + ], + "blockHash": "0x2cb63fb3f9764280061a40fa6993b5281ae775f22ae71db045d05f2b90f1c9d0", + "blockNumber": "0xabc1", + "chainId": "0x7e3", + "from": "0xca7a99380131e6c76cfa622396347107aeedca2d", + "gas": "0x7a120", + "hash": "0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19", + "input": "0x", + "maxFeePerGas": "0x5d21dba00", + "maxPriorityFeePerGas": "0x5d21dba00", + "nonce": "0x0", + "senderTxHash": "0x3c43345866a6769f66a1d1e1866674751b39ff7db5061a6e4c6c1916e6395b19", + "signatures": [ + { + "V": "0x0", + "R": "0xef11e538b4ae74704c26d0d23da0d93fea4ca65a1d9a924819b43fa6aeee3923", + "S": "0x2456ffa6da778525e44634306a81ada9effbbc2ab63c272a8ae208baac4e3deb" + } + ], + "to": "0x3e2ac308cd78ac2fe162f9522deb2b56d9da9499", + "transactionIndex": "0x0", + "type": "TxTypeEthereumDynamicFee", + "typeInt": 30722, + "value": "0x1" } } \ No newline at end of file