├── .dockerignore ├── .git-blame-ignore-revs ├── .gitattributes ├── .github └── workflows │ ├── client.yml │ ├── contracts.yml │ ├── docs.yml │ ├── format.yml │ ├── initcontainer.yml │ ├── npm.yml │ └── staker-rewards.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .prettierignore ├── .prettierrc.js ├── CONTRIBUTING.adoc ├── Dockerfile ├── LICENSE ├── README.adoc ├── SECURITY.adoc ├── cmd ├── cli_celo.go ├── cli_ethereum.go ├── connector_celo.go ├── connector_ethereum.go ├── helpers.go ├── resolve_bitcoin_address.go ├── signing.go ├── signing_celo.go ├── signing_celo_test.go ├── signing_ethereum.go ├── signing_ethereum_test.go ├── signing_ethlike.go └── start.go ├── config ├── config.go ├── config_test.go └── time │ └── time.go ├── configs └── config.toml.SAMPLE ├── crytic-config.json ├── docs ├── img │ └── ledger-live-add-account.png ├── infrastructure.adoc ├── network-troubleshooting.adoc ├── rfc │ ├── rfc-1.adoc │ └── rfc-2.adoc ├── run-keep-ecdsa.adoc └── tbtc-liquidation-recovery.adoc ├── go.mod ├── go.sum ├── infrastructure ├── kube │ ├── keep-dev │ │ ├── keep-ecdsa-0-service.yaml │ │ ├── keep-ecdsa-0-statefulset.yaml │ │ ├── keep-ecdsa-1-service.yaml │ │ ├── keep-ecdsa-1-statefulset.yaml │ │ ├── keep-ecdsa-2-service.yaml │ │ ├── keep-ecdsa-2-statefulset.yaml │ │ ├── keep-ecdsa-3-service.yaml │ │ ├── keep-ecdsa-3-statefulset.yaml │ │ ├── keep-ecdsa-4-service.yaml │ │ └── keep-ecdsa-4-statefulset.yaml │ ├── keep-test │ │ ├── celo │ │ │ ├── celo-network-alfajores-configmap.yaml │ │ │ ├── celo-network-alfajores-secret.yaml.SAMPLE │ │ │ ├── keep-ecdsa-0-service.yaml │ │ │ ├── keep-ecdsa-0-statefulset.yaml │ │ │ ├── keep-ecdsa-1-service.yaml │ │ │ ├── keep-ecdsa-1-statefulset.yaml │ │ │ ├── keep-ecdsa-2-service.yaml │ │ │ ├── keep-ecdsa-2-statefulset.yaml │ │ │ ├── keep-ecdsa-3-service.yaml │ │ │ ├── keep-ecdsa-3-statefulset.yaml │ │ │ ├── keep-ecdsa-4-service.yaml │ │ │ ├── keep-ecdsa-4-statefulset.yaml │ │ │ ├── keep-ecdsa-5-service.yaml │ │ │ ├── keep-ecdsa-5-statefulset.yaml │ │ │ ├── keep-ecdsa-6-service.yaml │ │ │ ├── keep-ecdsa-6-statefulset.yaml │ │ │ ├── keep-ecdsa-7-service.yaml │ │ │ ├── keep-ecdsa-7-statefulset.yaml │ │ │ ├── keep-ecdsa-8-service.yaml │ │ │ ├── keep-ecdsa-8-statefulset.yaml │ │ │ ├── keep-ecdsa-9-service.yaml │ │ │ └── keep-ecdsa-9-statefulset.yaml │ │ └── ethereum │ │ │ ├── keep-ecdsa-0-service.yaml │ │ │ ├── keep-ecdsa-0-statefulset.yaml │ │ │ ├── keep-ecdsa-1-service.yaml │ │ │ ├── keep-ecdsa-1-statefulset.yaml │ │ │ ├── keep-ecdsa-2-service.yaml │ │ │ ├── keep-ecdsa-2-statefulset.yaml │ │ │ ├── keep-ecdsa-3-service.yaml │ │ │ ├── keep-ecdsa-3-statefulset.yaml │ │ │ ├── keep-ecdsa-4-service.yaml │ │ │ ├── keep-ecdsa-4-statefulset.yaml │ │ │ ├── keep-ecdsa-5-service.yaml │ │ │ ├── keep-ecdsa-5-statefulset.yaml │ │ │ ├── keep-ecdsa-6-service.yaml │ │ │ ├── keep-ecdsa-6-statefulset.yaml │ │ │ ├── keep-ecdsa-7-service.yaml │ │ │ ├── keep-ecdsa-7-statefulset.yaml │ │ │ ├── keep-ecdsa-8-service.yaml │ │ │ ├── keep-ecdsa-8-statefulset.yaml │ │ │ ├── keep-ecdsa-9-service.yaml │ │ │ └── keep-ecdsa-9-statefulset.yaml │ └── templates │ │ └── keep-ecdsa │ │ └── initcontainer │ │ └── provision-keep-ecdsa │ │ ├── Dockerfile │ │ ├── keep-ecdsa-config-template.toml │ │ ├── package-lock.json │ │ ├── package.json │ │ └── provision-keep-ecdsa.js └── stakedrop-demo │ ├── activity-log.adoc │ └── config │ ├── config.toml │ ├── keep-ecdsa-operator-account-address.txt │ ├── keep-ecdsa-operator-account-keyfile │ └── keep-ecdsa-operator-account-password.txt ├── internal ├── testdata │ ├── btc_transactions.go │ ├── celo_key.json │ ├── config.toml │ ├── eth_key.json │ ├── tss.go │ └── tss │ │ ├── keygen_data_0.json │ │ ├── keygen_data_1.json │ │ ├── keygen_data_2.json │ │ ├── keygen_data_3.json │ │ └── keygen_data_4.json └── testhelper │ └── registry.go ├── main.go ├── package-lock.json ├── package.json ├── pkg ├── chain │ ├── bitcoin │ │ ├── config.go │ │ ├── derive_address.go │ │ ├── derive_address_test.go │ │ ├── electrs.go │ │ ├── electrs_test.go │ │ └── handle.go │ ├── celo │ │ ├── balance_monitoring.go │ │ ├── bonded_ecdsa_keep_handle.go │ │ ├── celo.go │ │ ├── connect.go │ │ ├── ids.go │ │ ├── stake_monitor.go │ │ └── tbtc.go │ ├── chain.go │ ├── config.go │ ├── ethereum │ │ ├── balance_monitoring.go │ │ ├── bonded_ecdsa_keep_handle.go │ │ ├── connect.go │ │ ├── ethereum.go │ │ ├── ids.go │ │ ├── stake_monitor.go │ │ └── tbtc.go │ ├── event.go │ ├── gen │ │ ├── celo │ │ │ ├── Makefile │ │ │ ├── abi │ │ │ │ └── BondedECDSAKeep.go │ │ │ ├── cmd │ │ │ │ ├── BondedECDSAKeep.go │ │ │ │ ├── BondedECDSAKeepFactory.go │ │ │ │ ├── BondedECDSAKeepVendor.go │ │ │ │ └── cmd.go │ │ │ ├── contract │ │ │ │ ├── .keep │ │ │ │ ├── BondedECDSAKeep.go │ │ │ │ ├── BondedECDSAKeepFactory.go │ │ │ │ └── BondedECDSAKeepVendor.go │ │ │ └── gen.go │ │ └── ethereum │ │ │ ├── Makefile │ │ │ ├── abi │ │ │ └── BondedECDSAKeep.go │ │ │ ├── cmd │ │ │ ├── BondedECDSAKeep.go │ │ │ ├── BondedECDSAKeepFactory.go │ │ │ ├── BondedECDSAKeepVendor.go │ │ │ └── cmd.go │ │ │ ├── contract │ │ │ ├── .keep │ │ │ ├── BondedECDSAKeep.go │ │ │ ├── BondedECDSAKeepFactory.go │ │ │ └── BondedECDSAKeepVendor.go │ │ │ └── gen.go │ ├── key.go │ ├── key_test.go │ ├── local │ │ ├── bonded_ecdsa_keep.go │ │ ├── bonded_ecdsa_keep_factory.go │ │ ├── bonded_ecdsa_keep_factory_test.go │ │ ├── bonded_ecdsa_keep_test.go │ │ ├── ids.go │ │ ├── local.go │ │ ├── local_test.go │ │ ├── tbtc.go │ │ └── tbtc_test.go │ ├── tbtc.go │ └── tbtc_test.go ├── client │ ├── client.go │ ├── config.go │ ├── event │ │ ├── deduplicator.go │ │ ├── deduplicator_test.go │ │ ├── tracking.go │ │ └── tracking_test.go │ ├── liquidation_recovery.go │ ├── liquidation_recovery_test.go │ └── registration.go ├── ecdsa │ ├── ecdsa.go │ ├── ecdsa_test.go │ └── tss │ │ ├── config.go │ │ ├── error.go │ │ ├── gen │ │ ├── gen.go │ │ └── pb │ │ │ ├── message.pb.go │ │ │ ├── message.proto │ │ │ ├── signer.pb.go │ │ │ └── signer.proto │ │ ├── keygen.go │ │ ├── marshaling.go │ │ ├── marshaling_test.go │ │ ├── member.go │ │ ├── member_test.go │ │ ├── message.go │ │ ├── network.go │ │ ├── params │ │ ├── params_box.go │ │ └── params_box_test.go │ │ ├── protocol_announce.go │ │ ├── protocol_announce_test.go │ │ ├── protocol_ready.go │ │ ├── protocol_ready_test.go │ │ ├── protocol_recovery.go │ │ ├── signer.go │ │ ├── signing.go │ │ ├── tss.go │ │ ├── tss_celo_test.go │ │ ├── tss_ethereum_test.go │ │ └── tss_test.go ├── extensions │ └── tbtc │ │ ├── config.go │ │ ├── recovery │ │ ├── recovery.go │ │ ├── recovery_test.go │ │ ├── resolve_address.go │ │ ├── resolve_address_test.go │ │ ├── storage.go │ │ └── storage_test.go │ │ ├── tbtc.go │ │ └── tbtc_test.go ├── firewall │ ├── firewall.go │ └── firewall_test.go ├── metrics │ └── metrics.go ├── node │ ├── node.go │ ├── params_pool.go │ └── params_pool_test.go ├── registry │ ├── keeps.go │ ├── keeps_test.go │ └── storage.go └── utils │ ├── btc_transaction.go │ ├── btc_transaction_test.go │ ├── byteutils │ ├── byteutils.go │ └── byteutils_test.go │ └── pbutils │ └── pbutils.go ├── scripts ├── initialize-celo.sh ├── initialize.sh ├── install-celo.sh ├── install.sh ├── macos-setup.sh └── start.sh ├── solidity ├── .babelrc ├── .eslintrc ├── .soliumrc.json ├── README.md ├── contracts │ ├── AbstractBondedECDSAKeep.sol │ ├── AbstractBonding.sol │ ├── BondedECDSAKeep.sol │ ├── BondedECDSAKeepFactory.sol │ ├── BondedECDSAKeepVendor.sol │ ├── BondedECDSAKeepVendorImplV1.sol │ ├── CandidatesPools.sol │ ├── CloneFactory.sol │ ├── ECDSABackportRewards.sol │ ├── ECDSARewards.sol │ ├── ECDSARewardsDistributor.sol │ ├── ECDSARewardsDistributorEscrow.sol │ ├── ECDSARewardsEscrowBeneficiary.sol │ ├── GroupSelectionSeed.sol │ ├── KeepBonding.sol │ ├── KeepCreator.sol │ ├── LPRewards.sol │ ├── Migrations.sol │ ├── api │ │ ├── IBondedECDSAKeep.sol │ │ ├── IBondedECDSAKeepFactory.sol │ │ ├── IBondedECDSAKeepVendor.sol │ │ └── IBondingManagement.sol │ ├── fully-backed │ │ ├── FullyBackedBonding.sol │ │ ├── FullyBackedECDSAKeep.sol │ │ └── FullyBackedECDSAKeepFactory.sol │ └── test │ │ ├── AbstractBondingStub.sol │ │ ├── BondedECDSAKeepCloneFactoryStub.sol │ │ ├── BondedECDSAKeepFactoryStub.sol │ │ ├── BondedECDSAKeepFactoryVendorStub.sol │ │ ├── BondedECDSAKeepStub.sol │ │ ├── BondedECDSAKeepVendorImplV1Stub.sol │ │ ├── BondedECDSAKeepVendorImplV2Stub.sol │ │ ├── ECDSARewardsStub.sol │ │ ├── FullyBackedBondingStub.sol │ │ ├── FullyBackedECDSAKeepCloneFactoryStub.sol │ │ ├── FullyBackedECDSAKeepFactoryStub.sol │ │ ├── FullyBackedECDSAKeepStub.sol │ │ ├── LPRewardsStaker.sol │ │ ├── ManagedGrantStub.sol │ │ ├── RandomBeaconStub.sol │ │ ├── StakingInfoStub.sol │ │ ├── TestEtherReceiver.sol │ │ ├── TestToken.sol │ │ ├── TokenGrantStub.sol │ │ ├── TokenStakingStub.sol │ │ └── integration │ │ └── ExternalContractsIntegration.sol ├── integration │ ├── sign_with_existing_keep.js │ ├── smoke_test.js │ └── stress_test.js ├── migrations │ ├── 1_initial_migration.js │ ├── 2_deploy_contracts.js │ └── 3_initialize.js ├── package-lock.json ├── package.json ├── scripts │ ├── etherscan-verify.sh │ ├── generate-staker-rewards-input.js │ ├── get-default-application-account.js │ ├── get-network-id.js │ ├── initialize-ecds-rewards.js │ ├── initialize-ecdsa-rewards-distributor.js │ ├── lcl-client-config.js │ ├── lcl-initialize.js │ ├── lcl-set-client-address.sh │ ├── liquidity-rewards-starter.js │ ├── recover-bonds.js │ └── unlock-eth-accounts.js ├── slither.config.json ├── tenderly.yaml ├── test-environment.config.js ├── test │ ├── AbstractBondingTest.js │ ├── BondedECDSAKeepFactoryTest.js │ ├── BondedECDSAKeepIntegrationTest.js │ ├── BondedECDSAKeepTest.js │ ├── BondedECDSAKeepVendorImplV1Test.js │ ├── BondedECDSAKeepVendorImplV1viaProxyTest.js │ ├── BondedECDSAKeepVendorTest.js │ ├── BondedECDSAKeepVendorUpgradeTest.js │ ├── FullyBackedBondingTest.js │ ├── FullyBackedECDSAKeepFactoryTest.js │ ├── FullyBackedECDSAKeepTest.js │ ├── KeepBondingTest.js │ ├── LPRewardsTest.js │ ├── helpers │ │ ├── generateTickets.js │ │ ├── increaseTime.js │ │ ├── listBalanceUtils.js │ │ ├── mineBlocks.js │ │ ├── packTicket.js │ │ ├── snapshot.js │ │ └── waitForEvent.js │ ├── integration │ │ └── keep_signing_e2e.js │ ├── rewards │ │ ├── TestECDSABackportRewards.js │ │ ├── TestECDSARewards.js │ │ ├── TestECDSARewardsDistributorEscrow.js │ │ └── rewardsSetup.js │ └── staker-rewards │ │ ├── MerkleDistributorTest.js │ │ └── rewardsData.js └── truffle.js └── staker-rewards ├── .eslintignore ├── .eslintrc ├── README.adoc ├── block-by-date.sh ├── data ├── cache-keeps.json └── cache-transactions.json ├── distributor ├── generate-merkle-root.ts ├── output-merkle-objects.json ├── package-lock.json └── package.json ├── lib ├── assets-calculator.js ├── cache.js ├── context.js ├── contract-helper.js ├── fraud-detector.js ├── keep.js ├── numbers.js ├── requirements.js ├── rewards-calculator.js ├── sla-calculator.js └── tenderly.js ├── package-lock.json ├── package.json ├── reports ├── 2020-11-13_2020-11-20.log.txt ├── 2020-11-13_2020-11-20.summary.txt ├── 2020-11-20_2020-11-27.log.txt ├── 2020-11-20_2020-11-27.summary.txt ├── 2020-11-27_2020-12-04.log.txt ├── 2020-11-27_2020-12-04.summary.txt ├── 2020-12-04_2020-12-11.log.txt ├── 2020-12-04_2020-12-11.summary.txt ├── 2020-12-11_2020-12-18.log.txt ├── 2020-12-11_2020-12-18.summary.txt ├── 2020-12-18_2020-12-25.log.txt ├── 2020-12-18_2020-12-25.summary.txt ├── 2020-12-25_2021-01-01.log.txt ├── 2020-12-25_2021-01-01.summary.txt ├── 2021-01-01_2021-01-08.log.txt ├── 2021-01-01_2021-01-08.summary.txt ├── 2021-01-08_2021-01-15.log.txt ├── 2021-01-08_2021-01-15.summary.txt ├── 2021-01-15_2021_01_22.log.txt ├── 2021-01-15_2021_01_22.summary.txt ├── 2021-01-22_2021-01-29.log.txt ├── 2021-01-22_2021-01-29.summary.txt ├── 2021-01-29_2021-02-05.log.txt ├── 2021-01-29_2021-02-05.summary.txt ├── 2021-02-05_2021-02-12.log.txt ├── 2021-02-05_2021-02-12.summary.txt ├── 2021-02-12_2021-02-19.log.txt ├── 2021-02-12_2021-02-19.summary.txt ├── 2021-02-19_2021-02-26.log.txt ├── 2021-02-19_2021-02-26.summary.txt ├── 2021-02-26_2021-03-05.log.txt ├── 2021-02-26_2021-03-05.summary.txt ├── 2021-03-05_2021-03-12.log.txt ├── 2021-03-05_2021-03-12.summary.txt ├── 2021-03-12_2021-03-19.log.txt ├── 2021-03-12_2021-03-19.summary.txt ├── 2021-03-19_2021-03-26.log.txt ├── 2021-03-19_2021-03-26.summary.txt ├── 2021-03-26_2021-04-02.log.txt ├── 2021-03-26_2021-04-02.summary.txt ├── 2021-04-02_2021-04-09.log.txt ├── 2021-04-02_2021-04-09.summary.txt ├── 2021-04-09_2021-04-16.log.txt ├── 2021-04-09_2021-04-16.summary.txt ├── 2021-04-16_2021-04-23.log.txt ├── 2021-04-16_2021-04-23.summary.txt ├── 2021-04-23_2021-04-30.log.txt ├── 2021-04-23_2021-04-30.summary.txt ├── 2021-04-30_2021-05-07.log.txt ├── 2021-04-30_2021-05-07.summary.txt ├── 2021-05-07_2021-05-14.log.txt ├── 2021-05-07_2021-05-14.summary.txt ├── 2021-05-14_2021-05-21.log.txt ├── 2021-05-14_2021-05-21.summary.txt ├── 2021-05-21_2021-05-28.log.txt ├── 2021-05-21_2021-05-28.summary.txt ├── 2021-05-28_2021-06-04.log.txt ├── 2021-05-28_2021-06-04.summary.txt ├── 2021-06-04_2021-06-11.log.txt ├── 2021-06-04_2021-06-11.summary.txt ├── 2021-06-11_2021-06-18.log.txt ├── 2021-06-11_2021-06-18.summary.txt ├── 2021-06-18_2021-06-25.log.txt ├── 2021-06-18_2021-06-25.summary.txt ├── 2021-06-25_2021-07-02.log.txt ├── 2021-06-25_2021-07-02.summary.txt ├── 2021-07-02_2021-07-09.log.txt ├── 2021-07-02_2021-07-09.summary.txt ├── 2021-07-09_2021-07-16.log.txt ├── 2021-07-09_2021-07-16.summary.txt ├── 2021-07-16_2021-07-23.log.txt ├── 2021-07-16_2021-07-23.summary.txt ├── 2021-07-23_2021-07-30.log.txt ├── 2021-07-23_2021-07-30.summary.txt ├── 2021-07-30_2021-08-06.log.txt ├── 2021-07-30_2021-08-06.summary.txt ├── 2021-08-06_2021-08-13.log.txt ├── 2021-08-06_2021-08-13.summary.txt ├── 2021-08-13_2021-08-20.log.txt ├── 2021-08-13_2021-08-20.summary.txt ├── 2021-08-20_2021-08-27.log.txt ├── 2021-08-20_2021-08-27.summary.txt ├── 2021-08-27_2021-09-03.log.txt ├── 2021-08-27_2021-09-03.summary.txt ├── 2021-09-03_2021-09-10.log.txt ├── 2021-09-03_2021-09-10.summary.txt ├── 2021-09-10_2021-09-17.log.txt ├── 2021-09-10_2021-09-17.summary.txt ├── 2021-09-17_2021-09-24.log.txt ├── 2021-09-17_2021-09-24.summary.txt ├── 2021-09-24_2021-10-01.log.txt ├── 2021-09-24_2021-10-01.summary.txt ├── 2021-10-01_2021-10-08.log.txt ├── 2021-10-01_2021-10-08.summary.txt ├── 2021-10-08_2021-10-15.log.txt ├── 2021-10-08_2021-10-15.summary.txt ├── 2021-10-15_2021-10-22.log.txt ├── 2021-10-15_2021-10-22.summary.txt ├── 2021-10-22_2021-10-29.log.txt ├── 2021-10-22_2021-10-29.summary.txt ├── 2021-10-29_2021-11-05.log.txt ├── 2021-10-29_2021-11-05.summary.txt ├── 2021-11-05_2021-11-12.log.txt ├── 2021-11-05_2021-11-12.summary.txt ├── 2021-11-12_2021-11-19.log.txt ├── 2021-11-12_2021-11-19.summary.txt ├── 2021-11-19_2021-11-26.log.txt ├── 2021-11-19_2021-11-26.summary.txt ├── 2021-11-26_2021-12-03.log.txt ├── 2021-11-26_2021-12-03.summary.txt ├── 2021-12-03_2021-12-21.log.txt ├── 2021-12-03_2021-12-21.summary.txt ├── 2021-12-21_2022-01-22.log.txt ├── 2021-12-21_2022-01-22.summary.txt ├── 2022-01-22_2022-02-22.log.txt ├── 2022-01-22_2022-02-22.summary.txt ├── 2022-02-22_2022-03-22.log.txt └── 2022-02-22_2022-03-22.summary.txt ├── rewards-merkle-generator.sh ├── rewards.js ├── rewards.png └── test ├── assets-calculator.test.js ├── data ├── test-blockchain.json ├── test-cache.json └── test-transactions.json ├── helpers ├── blockchain.js ├── constants.js └── mock.js ├── requirements.test.js ├── rewards-calculator.test.js └── sla-calculator.test.js /.dockerignore: -------------------------------------------------------------------------------- 1 | # Hidden files and directories. 2 | .* 3 | 4 | # Top-level directories unrelated to the build. 5 | docs/ 6 | infrastructure/ 7 | scripts/ 8 | staker-rewards/ 9 | 10 | # Top-level files unrealted to the build. 11 | CODEOWNERS 12 | crytic-config.json 13 | Dockerfile 14 | *.adoc 15 | 16 | # NPM stuff. 17 | **/node_modules/* 18 | 19 | # Solidity stuff. 20 | # We want to include only bare contracts and NPM package configuration for Go code 21 | # generator. 22 | solidity/ 23 | !solidity/contracts 24 | !solidity/package.json 25 | !solidity/package-lock.json 26 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Prettier auto-formatting 2 | e734d0e613869959eb28eb2104cb44a98d9994d0 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Code Format Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | code-format: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: "14.x" 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Check formatting 24 | run: npm run format 25 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: NPM 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "solidity/contracts/**" 9 | - "solidity/package.json" 10 | - "solidity/package-lock.json" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | npm-compile-publish-contracts: 15 | runs-on: ubuntu-latest 16 | defaults: 17 | run: 18 | working-directory: ./solidity 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: actions/setup-node@v2 23 | with: 24 | node-version: "14.x" 25 | cache: "npm" 26 | cache-dependency-path: solidity/package-lock.json 27 | registry-url: "https://registry.npmjs.org" 28 | 29 | - name: Install dependencies 30 | run: npm ci 31 | 32 | - name: Resolve latest contracts 33 | working-directory: ./solidity 34 | run: | 35 | npm update --save-exact @keep-network/keep-core 36 | 37 | - name: Compile contracts 38 | run: npx truffle compile 39 | 40 | - name: Copy artifacts 41 | run: | 42 | mkdir -p artifacts 43 | cp -r build/contracts/* artifacts/ 44 | 45 | - name: Bump up package version 46 | id: npm-version-bump 47 | uses: keep-network/npm-version-bump@v2 48 | with: 49 | work-dir: ./solidity 50 | environment: dev 51 | branch: ${{ github.ref }} 52 | commit: ${{ github.sha }} 53 | 54 | - name: Publish package 55 | env: 56 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 57 | run: npm publish --access public --tag development 58 | -------------------------------------------------------------------------------- /.github/workflows/staker-rewards.yml: -------------------------------------------------------------------------------- 1 | name: Staker Rewards 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "staker-rewards/**" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | staker-rewards-detect-changes: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | path-filter: ${{ steps.filter.outputs.path-filter }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | if: github.event_name == 'pull_request' 20 | - uses: dorny/paths-filter@v2 21 | if: github.event_name == 'pull_request' 22 | id: filter 23 | with: 24 | filters: | 25 | path-filter: 26 | - './staker-rewards/**' 27 | 28 | staker-rewards-test: 29 | needs: staker-rewards-detect-changes 30 | if: | 31 | github.event_name != 'pull_request' 32 | || needs.staker-rewards-detect-changes.outputs.path-filter == 'true' 33 | runs-on: ubuntu-latest 34 | defaults: 35 | run: 36 | working-directory: ./staker-rewards 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - uses: actions/setup-node@v2 41 | with: 42 | node-version: "14.x" 43 | cache: "npm" 44 | cache-dependency-path: staker-rewards/package-lock.json 45 | 46 | - name: Install dependencies 47 | run: npm ci 48 | 49 | - name: Run tests 50 | run: npm test 51 | 52 | staker-rewards-lint: 53 | needs: staker-rewards-detect-changes 54 | if: | 55 | github.event_name != 'pull_request' 56 | && needs.staker-rewards-detect-changes.outputs.path-filter == 'true' 57 | runs-on: ubuntu-latest 58 | defaults: 59 | run: 60 | working-directory: ./staker-rewards 61 | steps: 62 | - uses: actions/checkout@v2 63 | 64 | - uses: actions/setup-node@v2 65 | with: 66 | node-version: "14.x" 67 | 68 | - name: Cache node modules 69 | uses: actions/cache@v2 70 | env: 71 | cache-name: cache-solidity-node-modules 72 | with: 73 | path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS 74 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 75 | restore-keys: | 76 | ${{ runner.os }}-build-${{ env.cache-name }}- 77 | ${{ runner.os }}-build- 78 | ${{ runner.os }}- 79 | 80 | - name: Install dependencies 81 | run: npm ci 82 | 83 | - name: Lint 84 | run: npm run lint 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.bin 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Application dependencies 16 | vendor/ 17 | node_modules/ 18 | 19 | # IDE configuration 20 | .vscode 21 | 22 | # Generated files 23 | *.abi 24 | abi/ 25 | .DS_Store 26 | artifacts/ 27 | docs/**/*.html 28 | 29 | # Build output 30 | build/ 31 | 32 | # Configuration files 33 | configs 34 | !configs/config.toml.SAMPLE 35 | external-contracts.js-e 36 | staker-rewards/distributor/staker-reward-allocation.json 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "staker-rewards/merkle-distributor"] 2 | path = staker-rewards/merkle-distributor 3 | url = https://github.com/keep-network/merkle-distributor.git 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/keep-network/pre-commit-golang.git 3 | rev: 4cc73f21101f9da1208719b02bbbe0a4c491497e 4 | hooks: 5 | - id: go-imports 6 | - id: go-vet 7 | - id: go-lint 8 | - repo: https://github.com/keep-network/pre-commit-hooks.git 9 | rev: 63e729f 10 | hooks: 11 | - id: check-added-large-files 12 | - repo: local 13 | hooks: 14 | - id: solidity-lint-js 15 | name: "solidity lint js" 16 | entry: /usr/bin/env bash -c "cd solidity && npm run lint:js" 17 | files: 'solidity\/.*\.js$' 18 | language: script 19 | description: "Checks JS code according to the package's linter configuration" 20 | - id: solidity-lint-sol 21 | name: "solidity lint solidity" 22 | entry: /usr/bin/env bash -c "cd solidity && npm run lint:sol" 23 | files: '\.sol$' 24 | language: script 25 | description: "Checks Solidity code according to the package's linter configuration" 26 | - id: staker-rewards-lint-js 27 | name: "staker rewards lint js" 28 | entry: /usr/bin/env bash -c "cd staker-rewards && npm run lint:js" 29 | files: 'staker-rewards\/.*\.js$' 30 | language: script 31 | description: "Checks JS code according to the package's linter configuration" 32 | - id: format 33 | name: "code formatting" 34 | entry: /usr/bin/env bash -c "npm run format" 35 | exclude: '^staker-rewards\/|^solidity\/' 36 | language: script 37 | description: "Checks files formatting with prettier" 38 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Generated files 2 | docs/**/*.html 3 | 4 | # Following directories have their own formatting configuration: 5 | solidity/ 6 | staker-rewards/ 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("@keep-network/prettier-config-keep"), 3 | plugins: ["prettier-plugin-sh", "prettier-plugin-toml"], 4 | overrides: [ 5 | { 6 | files: "*.toml.SAMPLE", 7 | options: { 8 | parser: "toml", 9 | }, 10 | }, 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.adoc: -------------------------------------------------------------------------------- 1 | = Keep Contribution Guide 2 | 3 | 🎉 Thanks for taking the time to contribute! 🎉 Contributions are welcome from 4 | anyone, and even the smallest of fixes is appreciated! 5 | 6 | The following is a set of guidelines for contributing to Keep and its packages. 7 | These are mostly guidelines, not rules. Use your best judgment, and feel free to 8 | propose changes to this document in a pull request. 9 | 10 | == Getting started 11 | 12 | 1. Fork https://github.com/keep-network/keep-ecdsa[`keep-network/keep-ecdsa`] 13 | 2. Clone your fork 14 | 3. Follow the 15 | link:README.adoc#getting-set-up[installation & build steps] in the README. 16 | 4. Set up the <>. 17 | 5. Open a PR against the `main` branch and describe the change you are intending 18 | to undertake in the PR description. 19 | 20 | Before marking the PR as ready for review, make sure: 21 | 22 | * It passes the linter checks (see <> to make this automatic). 23 | * It passes the https://github.com/keep-network/keep-ecdsa/actions[continuous 24 | integration tests]. 25 | * Your changes have sufficient test coverage (e.g regression tests have 26 | been added for bug fixes, unit tests for new features) 27 | 28 | == Development Tooling 29 | 30 | Commits 31 | https://help.github.com/en/articles/about-commit-signature-verification[must 32 | be signed]. 33 | 34 | === Continuous Integration 35 | 36 | Keep uses https://github.com/keep-network/keep-ecdsa/actions[Github Actions] for 37 | continuous integration. All jobs must be green to merge a PR. 38 | 39 | === Pre-commit 40 | 41 | Pre-commit is a tool to install hooks that check code before commits are made. 42 | It can be helpful to install this, to automatically run linter checks and avoid 43 | pushing code that will not be accepted. Follow the 44 | https://pre-commit.com/[installation instructions here], and then run 45 | `pre-commit install` to install the hooks. Note that the 46 | `scripts/macos-setup.sh` script should automatically set this up for you. 47 | 48 | === Linting 49 | 50 | Linters and formatters for Solidity, JavaScript, and Go code are set up and run 51 | automatically as part of pre-commit hooks. These are checked again in CI builds 52 | to ensure they have been run and are passing. 53 | 54 | If you want to change a rule, or add a custom rule, to the JavaScript or 55 | Solidity linting, please propose these changes to our 56 | https://github.com/keep-network/solium-config-keep[solium-config-keep] and 57 | https://github.com/keep-network/eslint-config-keep[eslint-config-keep] packages. 58 | All other packages have it as a dependency. 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Keep SEZC. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /SECURITY.adoc: -------------------------------------------------------------------------------- 1 | The keep-ecdsa security policy matches that of the keep-core repository at 2 | https://github.com/keep-network/keep-core/tree/master/SECURITY.adoc. 3 | -------------------------------------------------------------------------------- /cmd/cli_celo.go: -------------------------------------------------------------------------------- 1 | //+build celo 2 | 3 | package cmd 4 | 5 | import ( 6 | chaincmd "github.com/keep-network/keep-ecdsa/pkg/chain/gen/celo/cmd" 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | // ChainCLICommand contains the definition of the celo command-line 11 | // subcommand and its own subcommands. 12 | var ChainCLICommand cli.Command 13 | 14 | const celoDescription = `The celo command allows interacting with Keep's Celo 15 | contracts directly. Each subcommand corresponds to one contract, and has 16 | subcommands corresponding to each method on that contract, which 17 | respectively each take parameters based on the contract method's parameters. 18 | 19 | See the subcommand help for additional details.` 20 | 21 | func init() { 22 | ChainCLICommand = cli.Command{ 23 | Name: "celo", 24 | Usage: `Provides access to Keep network Celo contracts.`, 25 | Description: celoDescription, 26 | Subcommands: chaincmd.AvailableCommands, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/cli_ethereum.go: -------------------------------------------------------------------------------- 1 | //+build !celo 2 | 3 | package cmd 4 | 5 | import ( 6 | chaincmd "github.com/keep-network/keep-ecdsa/pkg/chain/gen/ethereum/cmd" 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | // ChainCLICommand contains the definition of the ethereum command-line 11 | // subcommand and its own subcommands. 12 | var ChainCLICommand cli.Command 13 | 14 | const ethereumDescription = `The ethereum command allows interacting with Keep's Ethereum 15 | contracts directly. Each subcommand corresponds to one contract, and has 16 | subcommands corresponding to each method on that contract, which respectively 17 | each take parameters based on the contract method's parameters. 18 | 19 | See the subcommand help for additional details.` 20 | 21 | func init() { 22 | ChainCLICommand = cli.Command{ 23 | Name: "ethereum", 24 | Usage: `Provides access to Keep network Ethereum contracts.`, 25 | Description: ethereumDescription, 26 | Subcommands: chaincmd.AvailableCommands, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/connector_celo.go: -------------------------------------------------------------------------------- 1 | //+build celo 2 | 3 | package cmd 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "github.com/keep-network/keep-common/pkg/chain/celo/celoutil" 10 | "github.com/keep-network/keep-ecdsa/config" 11 | "github.com/keep-network/keep-ecdsa/pkg/chain" 12 | "github.com/keep-network/keep-ecdsa/pkg/chain/celo" 13 | ) 14 | 15 | func offlineChain( 16 | config *config.Config, 17 | ) (chain.OfflineHandle, error) { 18 | celoKey, err := celoutil.DecryptKeyFile( 19 | config.Celo.Account.KeyFile, 20 | config.Celo.Account.KeyFilePassword, 21 | ) 22 | if err != nil { 23 | return nil, fmt.Errorf( 24 | "failed to read key file [%s]: [%v]", 25 | config.Celo.Account.KeyFile, 26 | err, 27 | ) 28 | } 29 | 30 | return celo.Offline(celoKey, &config.Celo), nil 31 | } 32 | 33 | func connectChain( 34 | ctx context.Context, 35 | config *config.Config, 36 | ) (chain.Handle, *operatorKeys, error) { 37 | celoKey, err := celoutil.DecryptKeyFile( 38 | config.Celo.Account.KeyFile, 39 | config.Celo.Account.KeyFilePassword, 40 | ) 41 | if err != nil { 42 | return nil, nil, fmt.Errorf( 43 | "failed to read key file [%s]: [%v]", 44 | config.Celo.Account.KeyFile, 45 | err, 46 | ) 47 | } 48 | 49 | celoChain, err := celo.Connect(ctx, celoKey, &config.Celo) 50 | if err != nil { 51 | return nil, nil, fmt.Errorf( 52 | "failed to connect to celo node: [%v]", 53 | err, 54 | ) 55 | } 56 | 57 | operatorKeys := &operatorKeys{ 58 | public: &celoKey.PrivateKey.PublicKey, 59 | private: celoKey.PrivateKey, 60 | } 61 | 62 | return celoChain, operatorKeys, nil 63 | } 64 | 65 | func extractKeyFilePassword(config *config.Config) string { 66 | return config.Celo.Account.KeyFilePassword 67 | } 68 | -------------------------------------------------------------------------------- /cmd/resolve_bitcoin_address.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/keep-network/keep-ecdsa/config" 7 | "github.com/keep-network/keep-ecdsa/pkg/chain/bitcoin" 8 | "github.com/keep-network/keep-ecdsa/pkg/extensions/tbtc/recovery" 9 | 10 | "github.com/urfave/cli" 11 | ) 12 | 13 | // ResolveBitcoinBeneficiaryAddressCommand contains the definition of the 14 | // resolve-bitcoin-address command-line subcommand. 15 | var ResolveBitcoinBeneficiaryAddressCommand cli.Command 16 | 17 | const resolveBitcoinAddressDescription = `Uses details provided in the configuration ` + 18 | `file to estimate next Bitcoin beneficiary address that will be used for liquidation ` + 19 | `recovery.` 20 | 21 | func init() { 22 | ResolveBitcoinBeneficiaryAddressCommand = 23 | cli.Command{ 24 | Name: "resolve-bitcoin-address", 25 | Usage: `Resolves next available bitcoin address`, 26 | Description: resolveBitcoinAddressDescription, 27 | Action: ResolveBitcoinBeneficiaryAddress, 28 | } 29 | } 30 | 31 | // ResolveBitcoinBeneficiaryAddress resolves the next bitcoin address that would 32 | // be used for liquidation recovery based on the nodes configuration. 33 | func ResolveBitcoinBeneficiaryAddress(c *cli.Context) error { 34 | config, err := config.ReadConfig(c.GlobalString("config")) 35 | if err != nil { 36 | return fmt.Errorf("failed while reading config file: [%w]", err) 37 | } 38 | 39 | derivationIndexStorage, err := recovery.NewDerivationIndexStorage(config.Storage.DataDir) 40 | if err != nil { 41 | return fmt.Errorf("failed to initialize new derivation index storage: [%w]", err) 42 | } 43 | 44 | err = config.Extensions.TBTC.Bitcoin.Validate() 45 | if err != nil { 46 | return fmt.Errorf("invalid bitcoin configuration: [%w]", err) 47 | } 48 | 49 | tbtcConfig := config.Extensions.TBTC 50 | 51 | chainParams, err := tbtcConfig.Bitcoin.ChainParams() 52 | if err != nil { 53 | return fmt.Errorf( 54 | "failed to parse the configured net params: [%w]", 55 | err, 56 | ) 57 | } 58 | 59 | bitcoinHandle := bitcoin.Connect(tbtcConfig.Bitcoin.ElectrsURLWithDefault()) 60 | 61 | beneficiaryAddress, err := recovery.ResolveAddress( 62 | tbtcConfig.Bitcoin.BeneficiaryAddress, 63 | derivationIndexStorage, 64 | chainParams, 65 | bitcoinHandle, 66 | true, 67 | ) 68 | if err != nil { 69 | return fmt.Errorf( 70 | "failed to resolve a btc address from [%s]: [%w]", 71 | tbtcConfig.Bitcoin.BeneficiaryAddress, 72 | err, 73 | ) 74 | } 75 | 76 | logger.Infof("resolved bitcoin beneficiary address: %s", beneficiaryAddress) 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /config/time/time.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Duration is a work around to be able to parse values provided in more 8 | // friendly way, e.g. "4m20s". We have to do this because we use 9 | // BurntSushi/toml package to parse configuration files. Unfortunately it 10 | // doesn't support time.Duration out of the box. 11 | type Duration struct { 12 | time.Duration 13 | } 14 | 15 | // UnmarshalText deserializes the text byte array into a native Duration 16 | func (d *Duration) UnmarshalText(text []byte) error { 17 | var err error 18 | d.Duration, err = time.ParseDuration(string(text)) 19 | return err 20 | } 21 | 22 | // ToDuration converts our custom Duration to the equivalent `time.Duration` 23 | func (d *Duration) ToDuration() time.Duration { 24 | return time.Duration(d.Duration) 25 | } 26 | -------------------------------------------------------------------------------- /crytic-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cwd": "solidity" 3 | } 4 | -------------------------------------------------------------------------------- /docs/img/ledger-live-add-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keep-network/keep-ecdsa/1a4a46272b827896a7ae518bdfe4b825d8d5d0f3/docs/img/ledger-live-add-account.png -------------------------------------------------------------------------------- /docs/infrastructure.adoc: -------------------------------------------------------------------------------- 1 | :toc: macro 2 | :icons: font 3 | 4 | = Infrastructure 5 | 6 | :numbered: 7 | toc::[] 8 | 9 | == Deployment Considerations 10 | 11 | // TODO: Flesh out this document 12 | 13 | === Kubernetes 14 | 15 | At Keep we run on GCP + Kube. To accommodate the aforementioned system considerations we use the following pattern for each of our environments: 16 | 17 | - Regional Kube cluster. 18 | - 5 ECDSA clients, each running minimum stake required by the network. 19 | - A LoadBalancer Service for each client. 20 | - A StatefulSet for each client. 21 | 22 | You can see our Ropsten Kube configurations https://github.com/keep-network/keep-ecdsa/tree/master/infrastructure/kube/keep-test[here] 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/keep-network/keep-ecdsa 2 | 3 | go 1.16 4 | 5 | replace ( 6 | github.com/BurntSushi/toml => github.com/keep-network/toml v0.3.0 7 | github.com/blockcypher/gobcy => github.com/keep-network/gobcy v1.3.1 8 | github.com/btcsuite/btcd => github.com/keep-network/btcd v0.0.0-20190427004231-96897255fd17 9 | github.com/btcsuite/btcutil => github.com/keep-network/btcutil v0.0.0-20210527170813-e2ba6805a890 10 | github.com/urfave/cli => github.com/keep-network/cli v1.20.0 11 | github.com/binance-chain/tss-lib => github.com/keep-network/tss-lib v1.3.3-0.20220914135540-bf17762025bf // Branch: v1.3.4.keep 12 | ) 13 | 14 | require ( 15 | github.com/BurntSushi/toml v0.3.1 16 | github.com/binance-chain/tss-lib v1.3.1 17 | github.com/btcsuite/btcd v0.20.1-beta 18 | github.com/btcsuite/btcutil v1.0.2 19 | github.com/celo-org/celo-blockchain v0.0.0-20210222234634-f8c8f6744526 20 | github.com/ethereum/go-ethereum v1.10.8 21 | github.com/gogo/protobuf v1.3.2 22 | github.com/google/go-cmp v0.5.4 23 | github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa 24 | github.com/ipfs/go-log v1.0.4 25 | github.com/keep-network/keep-common v1.7.1-0.20211012131917-7102d7b9c6a0 26 | github.com/keep-network/keep-core v1.3.2-0.20211005093647-8e5d036364fa 27 | github.com/keep-network/tbtc v1.1.1-0.20211005102550-e0f035c575a2 28 | github.com/pkg/errors v0.9.1 29 | github.com/urfave/cli v1.22.1 30 | gotest.tools/v3 v3.0.3 31 | ) 32 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-dev/keep-ecdsa-0-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-0 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "0" 11 | spec: 12 | ports: 13 | - port: 3919 14 | targetPort: 3919 15 | name: tcp-3919 16 | selector: 17 | app: keep 18 | type: ecdsa 19 | id: "0" 20 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-dev/keep-ecdsa-1-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-1 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "1" 11 | spec: 12 | ports: 13 | - port: 3919 14 | targetPort: 3919 15 | name: tcp-3919 16 | selector: 17 | app: keep 18 | type: ecdsa 19 | id: "1" 20 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-dev/keep-ecdsa-2-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-2 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "2" 11 | spec: 12 | ports: 13 | - port: 3919 14 | targetPort: 3919 15 | name: tcp-3919 16 | selector: 17 | app: keep 18 | type: ecdsa 19 | id: "2" 20 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-dev/keep-ecdsa-3-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-3 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "3" 11 | spec: 12 | ports: 13 | - port: 3919 14 | targetPort: 3919 15 | name: tcp-3919 16 | selector: 17 | app: keep 18 | type: ecdsa 19 | id: "3" 20 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-dev/keep-ecdsa-4-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-4 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "4" 11 | spec: 12 | ports: 13 | - port: 3919 14 | targetPort: 3919 15 | name: tcp-3919 16 | selector: 17 | app: keep 18 | type: ecdsa 19 | id: "4" 20 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/celo-network-alfajores-configmap.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: celo-network-alfajores 5 | namespace: default 6 | data: 7 | network-id: "44787" 8 | contract-owner-celo-account-address: "0x2B2976824682233807A197081119dA511AF12F7a" 9 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/celo-network-alfajores-secret.yaml.SAMPLE: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: celo-network-alfajores 5 | namespace: default 6 | type: Opaque 7 | data: 8 | contract-owner-celo-account-private-key : "base64 encoded private key" 9 | keep-ecdsa-rpc-url: "base64 encoded url" 10 | keep-ecdsa-ws-url: "base64 encoded url" -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-0-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-0 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "0" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "0" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-1-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-1 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "1" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "1" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-2-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-2 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "2" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "2" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-3-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-3 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "3" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "3" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-4-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-4 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "4" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "4" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-5-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-5 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "5" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "5" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-6-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-6 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "6" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "6" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-7-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-7 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "7" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "7" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-8-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-8 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "8" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "8" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/celo/keep-ecdsa-9-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-celo-9 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "9" 11 | network: alfajores 12 | chain: celo 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | selector: 20 | app: keep 21 | type: ecdsa 22 | id: "9" 23 | network: alfajores 24 | chain: celo 25 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-0-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-0 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "0" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "0" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-1-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-1 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "1" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "1" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-2-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-2 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "2" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "2" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-3-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-3 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "3" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "3" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-4-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-4 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "4" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "4" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-5-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-5 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "5" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "5" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-6-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-6 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "6" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "6" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-7-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-7 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "7" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "7" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-8-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-8 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "8" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "8" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/keep-test/ethereum/keep-ecdsa-9-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: keep-ecdsa-9 6 | namespace: default 7 | labels: 8 | app: keep 9 | type: ecdsa 10 | id: "9" 11 | network: ropsten 12 | chain: ethereum 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 3919 17 | targetPort: 3919 18 | name: tcp-3919 19 | - port: 9601 20 | targetPort: 9601 21 | name: tcp-9601 22 | selector: 23 | app: keep 24 | type: ecdsa 25 | id: "9" 26 | network: ropsten 27 | chain: ethereum 28 | -------------------------------------------------------------------------------- /infrastructure/kube/templates/keep-ecdsa/initcontainer/provision-keep-ecdsa/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:11 AS runtime 2 | 3 | WORKDIR /tmp 4 | 5 | COPY ./package.json /tmp/package.json 6 | COPY ./package-lock.json /tmp/package-lock.json 7 | 8 | RUN npm install 9 | 10 | COPY ./BondedECDSAKeepFactory.json /tmp/BondedECDSAKeepFactory.json 11 | 12 | COPY ./KeepBonding.json /tmp/KeepBonding.json 13 | 14 | COPY ./TokenStaking.json /tmp/TokenStaking.json 15 | 16 | COPY ./KeepToken.json /tmp/KeepToken.json 17 | 18 | COPY ./TBTCSystem.json /tmp/TBTCSystem.json 19 | 20 | COPY ./keep-ecdsa-config-template.toml /tmp/keep-ecdsa-config-template.toml 21 | 22 | COPY ./provision-keep-ecdsa.js /tmp/provision-keep-ecdsa.js 23 | 24 | ENTRYPOINT ["node", "./provision-keep-ecdsa.js"] 25 | -------------------------------------------------------------------------------- /infrastructure/kube/templates/keep-ecdsa/initcontainer/provision-keep-ecdsa/keep-ecdsa-config-template.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML configuration file. 2 | # Connection details of Ethereum blockchain. 3 | [ethereum] 4 | URL = "" 5 | URLRPC = "" 6 | 7 | [ethereum.account] 8 | KeyFile = "" 9 | 10 | # Addresses of contracts deployed on ethereum blockchain. 11 | [ethereum.ContractAddresses] 12 | BondedECDSAKeepFactory = "" 13 | TBTCSystem = "" 14 | 15 | # Connection details of Celo blockchain. 16 | [celo] 17 | URL = "" 18 | URLRPC = "" 19 | 20 | [celo.account] 21 | KeyFile = "" 22 | 23 | # Addresses of contracts deployed on Celo blockchain. 24 | [celo.ContractAddresses] 25 | BondedECDSAKeepFactory = "" 26 | TBTCSystem = "" 27 | 28 | [LibP2P] 29 | Peers = [] 30 | Port = "" 31 | AnnouncedAddresses = [] 32 | 33 | [Storage] 34 | DataDir = "" 35 | 36 | [Metrics] 37 | Port = "" 38 | -------------------------------------------------------------------------------- /infrastructure/kube/templates/keep-ecdsa/initcontainer/provision-keep-ecdsa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@celo/contractkit": "^1.0.2", 4 | "@truffle/hdwallet-provider": "^1.0.38", 5 | "toml": "^3.0.0", 6 | "tomlify-j0.4": "^3.0.0", 7 | "web3": "1.2.9" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /infrastructure/stakedrop-demo/config/config.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML configuration file. 2 | # Connection details of ethereum blockchain. 3 | [ethereum] 4 | URL = "wss://eth-ropsten.ws.alchemyapi.io/v2/BfBlnbrARASF-TtU7Q3lcavbgnwW3ZDc" 5 | URLRPC = "https://eth-ropsten.alchemyapi.io/v2/BfBlnbrARASF-TtU7Q3lcavbgnwW3ZDc" 6 | 7 | [ethereum.account] 8 | KeyFile = "/mnt/keep-ecdsa-client/config/keep-ecdsa-operator-account-keyfile" 9 | 10 | # Addresses of contracts deployed on ethereum blockchain. 11 | [ethereum.ContractAddresses] 12 | BondedECDSAKeepFactory = "0x17caddf97a1d1123efb7b233cb16c76c31a96e02" 13 | 14 | # Addresses of applications approved by the operator. 15 | [SanctionedApplications] 16 | Addresses = ["0x2b70907b5c44897030ea1369591ddcd23c5d85d6"] 17 | 18 | [Storage] 19 | DataDir = "/mnt/keep-ecdsa-client/persistence" 20 | 21 | [LibP2P] 22 | Peers = [ 23 | "/dns4/bootstrap-1.ecdsa.keep.test.boar.network/tcp/4001/ipfs/16Uiu2HAmPFXDaeGWtnzd8s39NsaQguoWtKi77834A6xwYqeicq6N" 24 | ] 25 | Port = 3919 26 | # Uncomment to override the node's default addresses announced in the network 27 | AnnouncedAddresses = [] 28 | 29 | [TSS] 30 | # Timeout for TSS protocol pre-parameters generation. The value 31 | # should be provided based on resources available on the machine running the client. 32 | # This is an optional parameter, if not provided timeout for TSS protocol 33 | # pre-parameters generation will be set to `2 minutes`. 34 | PreParamsGenerationTimeout = "2m30s" 35 | -------------------------------------------------------------------------------- /infrastructure/stakedrop-demo/config/keep-ecdsa-operator-account-address.txt: -------------------------------------------------------------------------------- 1 | # THIS IS FOR DEMO PURPOSES ONLY 2 | # DO NOT USE THIS ACCOUNT EVERYONE HAS ACCESS 3 | 4 | 0x91c9122ee29ab47908f00dc2cafaa41454f1722f 5 | -------------------------------------------------------------------------------- /infrastructure/stakedrop-demo/config/keep-ecdsa-operator-account-keyfile: -------------------------------------------------------------------------------- 1 | {"address":"91c9122ee29ab47908f00dc2cafaa41454f1722f","crypto":{"cipher":"aes-128-ctr","ciphertext":"a164cc5264e825db37ec880a0be09a3664ce996445d8cf80e018d577be870790","cipherparams":{"iv":"7f0b2a86d76b386905e5ece81eb15869"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"c4a8ffd814c6b7ec5b598ef56ae9952bb8e5859e2f827b2ae06c6972aeccef6b"},"mac":"5406ad0c938ca1682b5639636cf63cd105499d15ca8e0b27b2bfb1ff1d57592b"},"id":"5a5825fe-c493-4d58-8476-297296217a88","version":3} -------------------------------------------------------------------------------- /infrastructure/stakedrop-demo/config/keep-ecdsa-operator-account-password.txt: -------------------------------------------------------------------------------- 1 | awkward castle pistol toss belt try ritual jar stage twenty firm trigger 2 | -------------------------------------------------------------------------------- /internal/testdata/btc_transactions.go: -------------------------------------------------------------------------------- 1 | // Package testdata contains transactions data used in tests. 2 | package testdata 3 | 4 | // Transaction holds transaction specific data. 5 | type Transaction struct { 6 | Hash string // hash of the transaction 7 | PreviousTxHash string // hash of a previous transaction 8 | PreviousOutScript string // script sig of a previous transaction output 9 | PreviousOutAmount int64 // amount of a previous transaction output 10 | UnsignedRaw string // serialized transaction before signing 11 | WitnessSignatureHash string // witness signature hash for signing 12 | SignedRaw string // serialized transaction after signing 13 | } 14 | 15 | var ( 16 | // InitialTx is a prerequisite transaction used in tests to initialize a 17 | // chain. Next transaction is build based on this one. 18 | InitialTx = Transaction{ 19 | Hash: "5e13ca34cf527e7b443afc0d6958a67bf7950a11f6ec3e05f8e3f3e802fbdf99", 20 | SignedRaw: "0100000001f24d19b6980927dbe47c30fd13b1cc12e56a11cc019efed67a1b4d3937b74bab010000006a47304402201711a033c1b829719716c81419294214a7fce0f0f1f9f51b6821ca3a5beebbdd022059b7bdd0bf1fe08aa4b4654360732d2a1f97c602b2e198a41e7bc53d81376c9a0121028896955d043b5a43957b21901f2cce9f0bfb484531b03ad6cd3153e45e73ee2effffffff022823000000000000160014d849b1e1cede2ac7d7188cf8700e97d6975c91c4b2f9fd00000000001976a914d849b1e1cede2ac7d7188cf8700e97d6975c91c488ac00000000", 21 | } 22 | 23 | // ValidTx is a transaction build on an initial transaction. 24 | ValidTx = Transaction{ 25 | Hash: "ec367c260ead9e3c91583175f35382e22b66df6d59fd0aac175bb36519b664f7", 26 | PreviousTxHash: InitialTx.Hash, 27 | PreviousOutScript: "0014d849b1e1cede2ac7d7188cf8700e97d6975c91c4", 28 | PreviousOutAmount: int64(9000), 29 | UnsignedRaw: "010000000199dffb02e8f3e3f8053eecf6110a95f77ba658690dfc3a447b7e52cf34ca135e0000000000ffffffff02581b000000000000160014d849b1e1cede2ac7d7188cf8700e97d6975c91c4e8030000000000001976a914d849b1e1cede2ac7d7188cf8700e97d6975c91c488ac00000000", 30 | WitnessSignatureHash: "cc493a708e6ec962f2be8dc0a24c35966ee46f563de8bf219b9c5313a3b24e58", 31 | SignedRaw: "0100000000010199dffb02e8f3e3f8053eecf6110a95f77ba658690dfc3a447b7e52cf34ca135e0000000000ffffffff02581b000000000000160014d849b1e1cede2ac7d7188cf8700e97d6975c91c4e8030000000000001976a914d849b1e1cede2ac7d7188cf8700e97d6975c91c488ac02483045022100ecadce07f5c9d84b4fa1b2728806135acd81ad9398c9673eeb4e161d42364b92022076849daa2108ed2a135d16eb9e103c5819db014ea2bad5c92f4aeecf47bf9ac80121028896955d043b5a43957b21901f2cce9f0bfb484531b03ad6cd3153e45e73ee2e00000000", 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /internal/testdata/celo_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "4bcfc3099f12c53d01da46695cc8776be584b946", 3 | "crypto": { 4 | "cipher": "aes-128-ctr", 5 | "ciphertext": "904cb53d4e6c3e7dc6fb2443ad5db42b502e221b4144276ac150d51cea3cd638", 6 | "cipherparams": { 7 | "iv": "be8d7c7197c45d371cb6d97f9878636e" 8 | }, 9 | "kdf": "scrypt", 10 | "kdfparams": { 11 | "dklen": 32, 12 | "n": 262144, 13 | "p": 1, 14 | "r": 8, 15 | "salt": "b93d46f5ff9a9cf15a3546b05a0941d17e1cb9467e2484f5e1495a030616f258" 16 | }, 17 | "mac": "87bb79dbb0e0f056d8d2e91d7c7a7c18367a52fae5324b5c75423272d849860f" 18 | }, 19 | "id": "bffd9951-e141-41a3-9d63-82c8dd92c1d5", 20 | "version": 3 21 | } 22 | -------------------------------------------------------------------------------- /internal/testdata/config.toml: -------------------------------------------------------------------------------- 1 | [ethereum] 2 | URL = "ws://192.168.0.158:8546" 3 | URLRPC = "http://192.168.0.158:8545" 4 | MaxGasFeeCap = "140 Gwei" 5 | BalanceAlertThreshold = "2.5 ether" 6 | 7 | [ethereum.account] 8 | KeyFile = "/tmp/UTC--2018-03-11T01-37-33.202765887Z--c2a56884538778bacd91aa5bf343bf882c5fb18b" 9 | 10 | [ethereum.ContractAddresses] 11 | BondedECDSAKeepFactory = "0x2BBE98119100D664eb6dEe5b8DB978aEEeAf42D6" 12 | TBTCSystem = "0xda4c869B9073deac021344fd592c1BB0DC6Fc9a5" 13 | 14 | [Storage] 15 | DataDir = "/my/secure/location" 16 | 17 | [LibP2P] 18 | Port = 27001 19 | Peers = [ 20 | "/ip4/127.0.0.1/tcp/27001/ipfs/12D3KooWKRyzVWW6ChFjQjK4miCty85Niy49tpPV95XdKu1BcvMA" 21 | ] 22 | 23 | [Client] 24 | AwaitingKeyGenerationLookback = "48h" 25 | KeyGenerationTimeout = "1h45m" 26 | SigningTimeout = "3h30m" 27 | 28 | [TSS] 29 | PreParamsGenerationTimeout = "6m37s" 30 | PreParamsTargetPoolSize = 36 31 | 32 | [Extensions.TBTC] 33 | TBTCSystem = "0xa4888eDD97A5a3A739B4E0807C71817c8a418273" 34 | LiquidationRecoveryTimeout = "49h" 35 | 36 | [Extensions.TBTC.Bitcoin] 37 | BeneficiaryAddress = "xpub6Cg41S21VrxkW1WBTZJn95KNpHozP2Xc6AhG27ZcvZvH8XyNzunEqLdk9dxyXQUoy7ALWQFNn5K1me74aEMtS6pUgNDuCYTTMsJzCAk9sk1" 38 | MaxFeePerVByte = 73 39 | BitcoinChainName = "mainnet" 40 | ElectrsURL = "example.com" 41 | -------------------------------------------------------------------------------- /internal/testdata/eth_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "4bcfc3099f12c53d01da46695cc8776be584b946", 3 | "crypto": { 4 | "cipher": "aes-128-ctr", 5 | "ciphertext": "904cb53d4e6c3e7dc6fb2443ad5db42b502e221b4144276ac150d51cea3cd638", 6 | "cipherparams": { 7 | "iv": "be8d7c7197c45d371cb6d97f9878636e" 8 | }, 9 | "kdf": "scrypt", 10 | "kdfparams": { 11 | "dklen": 32, 12 | "n": 262144, 13 | "p": 1, 14 | "r": 8, 15 | "salt": "b93d46f5ff9a9cf15a3546b05a0941d17e1cb9467e2484f5e1495a030616f258" 16 | }, 17 | "mac": "87bb79dbb0e0f056d8d2e91d7c7a7c18367a52fae5324b5c75423272d849860f" 18 | }, 19 | "id": "bffd9951-e141-41a3-9d63-82c8dd92c1d5", 20 | "version": 3 21 | } 22 | -------------------------------------------------------------------------------- /internal/testdata/tss.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "path/filepath" 8 | "runtime" 9 | 10 | "github.com/binance-chain/tss-lib/ecdsa/keygen" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | const ( 15 | testFixtureDirFormat = "%s/tss" 16 | testFixtureFileFormat = "keygen_data_%d.json" 17 | ) 18 | 19 | // LoadKeygenTestFixtures loads key generation test data. 20 | // Code copied from: 21 | // https://github.com/binance-chain/tss-lib/blob/master/ecdsa/keygen/test_utils.go 22 | // Test data JSON files copied from: 23 | // https://github.com/binance-chain/tss-lib/tree/master/test/_ecdsa_fixtures 24 | func LoadKeygenTestFixtures(count int) ([]keygen.LocalPartySaveData, error) { 25 | keys := make([]keygen.LocalPartySaveData, 0, count) 26 | for j := 0; j < count; j++ { 27 | fixtureFilePath := makeTestFixtureFilePath(j) 28 | 29 | // #nosec G304 (file path provided as taint input) 30 | // This line is used to read a test fixture file. 31 | // There is no user input. 32 | bz, err := ioutil.ReadFile(fixtureFilePath) 33 | if err != nil { 34 | return nil, errors.Wrapf(err, 35 | "could not open the test fixture for party %d in the expected location: %s. run keygen tests first.", 36 | j, fixtureFilePath) 37 | } 38 | var key keygen.LocalPartySaveData 39 | if err = json.Unmarshal(bz, &key); err != nil { 40 | return nil, errors.Wrapf(err, 41 | "could not unmarshal fixture data for party %d located at: %s", 42 | j, fixtureFilePath) 43 | } 44 | keys = append(keys, key) 45 | } 46 | return keys, nil 47 | } 48 | 49 | func makeTestFixtureFilePath(partyIndex int) string { 50 | _, callerFileName, _, _ := runtime.Caller(0) 51 | srcDirName := filepath.Dir(callerFileName) 52 | fixtureDirName := fmt.Sprintf(testFixtureDirFormat, srcDirName) 53 | return fmt.Sprintf("%s/"+testFixtureFileFormat, fixtureDirName, partyIndex) 54 | } 55 | -------------------------------------------------------------------------------- /internal/testhelper/registry.go: -------------------------------------------------------------------------------- 1 | package testhelper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/keep-network/keep-common/pkg/persistence" 7 | "github.com/keep-network/keep-ecdsa/pkg/ecdsa/tss" 8 | ) 9 | 10 | // PersistenceHandleMock is a mock of persistence handle used in tests. 11 | type PersistenceHandleMock struct { 12 | PersistedGroups []*TestFileInfo 13 | Snapshots []*TestFileInfo 14 | ArchivedGroups []string 15 | outputDataChan chan persistence.DataDescriptor 16 | outputErrorsChan chan error 17 | } 18 | 19 | // NewPersistenceHandleMock creates a mocked persistence handle. 20 | func NewPersistenceHandleMock(outputDataChanSize int) *PersistenceHandleMock { 21 | return &PersistenceHandleMock{ 22 | outputDataChan: make(chan persistence.DataDescriptor, outputDataChanSize), 23 | outputErrorsChan: make(chan error), 24 | } 25 | } 26 | 27 | // TestFileInfo holds test data stored in persistence handle. 28 | type TestFileInfo struct { 29 | Data []byte 30 | Directory string 31 | Name string 32 | } 33 | 34 | // Save stores data in persistence handle. 35 | func (phm *PersistenceHandleMock) Save(data []byte, directory string, name string) error { 36 | phm.PersistedGroups = append( 37 | phm.PersistedGroups, 38 | &TestFileInfo{data, directory, name}, 39 | ) 40 | 41 | return nil 42 | } 43 | 44 | // Snapshot creates a snapshot of data in persistence handle. 45 | func (phm *PersistenceHandleMock) Snapshot(data []byte, directory string, name string) error { 46 | phm.Snapshots = append( 47 | phm.Snapshots, 48 | &TestFileInfo{data, directory, name}, 49 | ) 50 | 51 | return nil 52 | } 53 | 54 | // MockSigner registers a mock of a signer for membership and keep. 55 | func (phm *PersistenceHandleMock) MockSigner(membershipIndex int, keepID string, signer *tss.ThresholdSigner) error { 56 | signerBytes, err := signer.Marshal() 57 | if err != nil { 58 | return fmt.Errorf("failed to marshal signer: %w", err) 59 | } 60 | 61 | phm.outputDataChan <- &testDataDescriptor{ 62 | fmt.Sprintf("/membership_%d", membershipIndex), 63 | keepID, 64 | signerBytes, 65 | } 66 | 67 | return nil 68 | } 69 | 70 | // ReadAll reads all data stored in persistence handle. 71 | func (phm *PersistenceHandleMock) ReadAll() (<-chan persistence.DataDescriptor, <-chan error) { 72 | close(phm.outputDataChan) 73 | close(phm.outputErrorsChan) 74 | 75 | return phm.outputDataChan, phm.outputErrorsChan 76 | } 77 | 78 | // Archive archives data in persistence handle. 79 | func (phm *PersistenceHandleMock) Archive(directory string) error { 80 | phm.ArchivedGroups = append(phm.ArchivedGroups, directory) 81 | phm.PersistedGroups = phm.PersistedGroups[:len(phm.ArchivedGroups)-1] 82 | 83 | return nil 84 | } 85 | 86 | type testDataDescriptor struct { 87 | name string 88 | directory string 89 | content []byte 90 | } 91 | 92 | func (tdd *testDataDescriptor) Name() string { 93 | return tdd.name 94 | } 95 | 96 | func (tdd *testDataDescriptor) Directory() string { 97 | return tdd.directory 98 | } 99 | 100 | func (tdd *testDataDescriptor) Content() ([]byte, error) { 101 | return tdd.content, nil 102 | } 103 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "time" 8 | 9 | "github.com/ipfs/go-log" 10 | "github.com/keep-network/keep-common/pkg/logging" 11 | "github.com/keep-network/keep-ecdsa/cmd" 12 | "github.com/urfave/cli" 13 | ) 14 | 15 | var logger = log.Logger("keep-main") 16 | 17 | const ( 18 | defaultConfigPath = "./configs/config.toml" 19 | ) 20 | 21 | var ( 22 | version string 23 | revision string 24 | 25 | configPath string 26 | ) 27 | 28 | func main() { 29 | if version == "" { 30 | version = "unknown" 31 | } 32 | if revision == "" { 33 | revision = "unknown" 34 | } 35 | 36 | err := logging.Configure(os.Getenv("LOG_LEVEL")) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "failed to configure logging: [%v]\n", err) 39 | } 40 | 41 | app := cli.NewApp() 42 | app.Name = path.Base(os.Args[0]) 43 | app.Usage = "CLI for t-ECDSA keep" 44 | app.Compiled = time.Now() 45 | app.Authors = []cli.Author{ 46 | { 47 | Name: "Keep Network", 48 | Email: "info@keep.network", 49 | }, 50 | } 51 | app.Version = fmt.Sprintf("%s (revision %s)", version, revision) 52 | app.Flags = []cli.Flag{ 53 | cli.StringFlag{ 54 | Name: "config,c", 55 | Value: defaultConfigPath, 56 | Destination: &configPath, 57 | Usage: "full path to the configuration file", 58 | }, 59 | } 60 | 61 | // TODO: implement Celo commands 62 | app.Commands = []cli.Command{ 63 | cmd.StartCommand, 64 | cmd.ChainCLICommand, 65 | cmd.SigningCommand, 66 | cmd.ResolveBitcoinBeneficiaryAddressCommand, 67 | } 68 | 69 | err = app.Run(os.Args) 70 | if err != nil { 71 | logger.Fatal(err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "format": "prettier --check .", 4 | "format:fix": "prettier --write ." 5 | }, 6 | "devDependencies": { 7 | "@keep-network/prettier-config-keep": "github:keep-network/prettier-config-keep", 8 | "prettier": "^2.3.0", 9 | "prettier-plugin-sh": "^0.6.1", 10 | "prettier-plugin-toml": "^0.3.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg/chain/bitcoin/config.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/btcsuite/btcd/chaincfg" 7 | ) 8 | 9 | // Config stores configuration related to recovering BTC from a closed keep. 10 | type Config struct { 11 | BeneficiaryAddress string 12 | MaxFeePerVByte int32 13 | BitcoinChainName string 14 | ElectrsURL *string 15 | } 16 | 17 | // Validate returns nil if the configuration is suitable for bitcoin recovery, 18 | // and an error detailing what went wrong if not. 19 | func (c Config) Validate() error { 20 | if c.BeneficiaryAddress == "" { 21 | return fmt.Errorf("a bitcoin address or extended public key (*pub) is required; configure one at [Extensions.TBTC.Bitcoin.BeneficiaryAddress]") 22 | } 23 | chainParams, err := c.ChainParams() 24 | if err != nil { 25 | return fmt.Errorf("a valid chain name is required; choose between [mainnet, regtest, simnet, testnet3] and configure it at [Extensions.TBTC.Bitcoin.BitcoinChainName]: [%w]", err) 26 | } 27 | err = ValidateAddressOrKey(c.BeneficiaryAddress, chainParams) 28 | if err != nil { 29 | return fmt.Errorf( 30 | "a valid bitcoin address or extended public key (*pub) is required; configure one at [Extensions.TBTC.Bitcoin.BeneficiaryAddress]: [%w]", 31 | err, 32 | ) 33 | } 34 | return nil 35 | } 36 | 37 | // ChainParams parses the net param name into the associated chaincfg.Params 38 | func (c Config) ChainParams() (*chaincfg.Params, error) { 39 | switch c.BitcoinChainName { 40 | case "mainnet", "": 41 | // If no chain name is provided, use the main net 42 | return &chaincfg.MainNetParams, nil 43 | case "regtest": 44 | return &chaincfg.RegressionNetParams, nil 45 | case "simnet": 46 | return &chaincfg.SimNetParams, nil 47 | case "testnet3": 48 | return &chaincfg.TestNet3Params, nil 49 | default: 50 | return nil, fmt.Errorf("unable to find chaincfg param for name: [%s]", c.BitcoinChainName) 51 | } 52 | } 53 | 54 | // ElectrsURLWithDefault dereferences ElectrsURL in the following way: if there 55 | // is a configured value, use it. Otherwise, default to 56 | // https://blockstream.info/api/. This allows us to add bitcoin connection 57 | // functionality to nodes that haven't made config changes yet while also 58 | // letting a user connect to the node of their choice. 59 | func (c Config) ElectrsURLWithDefault() string { 60 | if c.ElectrsURL == nil { 61 | if c.BitcoinChainName == "testnet3" { 62 | return "https://blockstream.info/testnet/api/" 63 | } 64 | 65 | return "https://blockstream.info/api/" 66 | } 67 | return *c.ElectrsURL 68 | } 69 | -------------------------------------------------------------------------------- /pkg/chain/bitcoin/handle.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | // Handle serves as an interface abstraction around bitcoin network queries 4 | type Handle interface { 5 | Broadcast(transaction string) error 6 | VbyteFeeFor25Blocks() (int32, error) 7 | IsAddressUnused(btcAddress string) (bool, error) 8 | } 9 | -------------------------------------------------------------------------------- /pkg/chain/celo/balance_monitoring.go: -------------------------------------------------------------------------------- 1 | //+build celo 2 | 3 | package celo 4 | 5 | import ( 6 | "context" 7 | "math/big" 8 | "time" 9 | 10 | "github.com/celo-org/celo-blockchain/common" 11 | "github.com/keep-network/keep-common/pkg/chain/celo" 12 | "github.com/keep-network/keep-common/pkg/chain/celo/celoutil" 13 | ) 14 | 15 | // Values related with balance monitoring. 16 | // 17 | // defaultBalanceAlertThreshold determines the alert threshold below which 18 | // the alert should be triggered. 19 | var defaultBalanceAlertThreshold = celo.WrapWei( 20 | big.NewInt(500000000000000000), 21 | ) 22 | 23 | // defaultBalanceMonitoringTick determines how often the monitoring 24 | // check should be triggered. 25 | const defaultBalanceMonitoringTick = 10 * time.Minute 26 | 27 | // defaultBalanceMonitoringRetryTimeout determines the timeout for balance check 28 | // at each tick. 29 | const defaultBalanceMonitoringRetryTimeout = 5 * time.Minute 30 | 31 | func (cc *celoChain) initializeBalanceMonitoring( 32 | ctx context.Context, 33 | ) { 34 | balanceMonitor, err := cc.balanceMonitor() 35 | if err != nil { 36 | logger.Errorf("error obtaining balance monitor handle [%v]", err) 37 | return 38 | } 39 | 40 | alertThreshold := defaultBalanceAlertThreshold 41 | if value := cc.config.BalanceAlertThreshold; value != nil { 42 | alertThreshold = value 43 | } 44 | 45 | balanceMonitor.Observe( 46 | ctx, 47 | cc.operatorAddress(), 48 | alertThreshold, 49 | defaultBalanceMonitoringTick, 50 | defaultBalanceMonitoringRetryTimeout, 51 | ) 52 | 53 | logger.Infof( 54 | "started balance monitoring for address [%v] "+ 55 | "with the alert threshold set to [%v]", 56 | cc.OperatorID(), 57 | alertThreshold, 58 | ) 59 | } 60 | 61 | // BalanceMonitor returns a balance monitor. 62 | func (cc *celoChain) balanceMonitor() (*celoutil.BalanceMonitor, error) { 63 | weiBalanceOf := func(address common.Address) (*celo.Wei, error) { 64 | return cc.weiBalanceOf(address) 65 | } 66 | 67 | return celoutil.NewBalanceMonitor(weiBalanceOf), nil 68 | } 69 | -------------------------------------------------------------------------------- /pkg/chain/celo/ids.go: -------------------------------------------------------------------------------- 1 | //+build celo 2 | 3 | package celo 4 | 5 | import ( 6 | cecdsa "crypto/ecdsa" 7 | "fmt" 8 | 9 | "github.com/celo-org/celo-blockchain/common" 10 | "github.com/celo-org/celo-blockchain/crypto" 11 | 12 | "github.com/keep-network/keep-ecdsa/pkg/chain" 13 | ) 14 | 15 | func (cc *celoChain) UnmarshalID(idString string) (chain.ID, error) { 16 | if !common.IsHexAddress(idString) { 17 | return nil, fmt.Errorf( 18 | "[%v] is not a valid celo ID", 19 | idString, 20 | ) 21 | } 22 | 23 | return celoChainID(common.HexToAddress(idString)), nil 24 | } 25 | 26 | func (cc *celoChain) PublicKeyToOperatorID(publicKey *cecdsa.PublicKey) chain.ID { 27 | return celoChainID(crypto.PubkeyToAddress(*publicKey)) 28 | } 29 | 30 | // celoChainID is the local chain.ID type; it is an alias for 31 | // go-ethereum/common.Address. 32 | type celoChainID common.Address 33 | 34 | func (ci celoChainID) ChainName() string { 35 | return "celo" 36 | } 37 | 38 | func (ci celoChainID) String() string { 39 | return common.Address(ci).Hex() 40 | } 41 | 42 | func (ci celoChainID) IsForChain(handle chain.Handle) bool { 43 | _, ok := handle.(*celoChain) 44 | 45 | return ok 46 | } 47 | 48 | func toIDSlice(addresses []common.Address) []chain.ID { 49 | memberIDs := make([]chain.ID, 0, len(addresses)) 50 | for _, address := range addresses { 51 | memberIDs = append(memberIDs, celoChainID(address)) 52 | } 53 | 54 | return memberIDs 55 | } 56 | 57 | func fromChainID(id chain.ID) (common.Address, error) { 58 | ci, ok := id.(celoChainID) 59 | if !ok { 60 | return common.Address{}, fmt.Errorf("failed to convert to celoChainID") 61 | } 62 | 63 | return common.Address(ci), nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/chain/celo/stake_monitor.go: -------------------------------------------------------------------------------- 1 | //+build celo 2 | 3 | package celo 4 | 5 | import ( 6 | "fmt" 7 | "math/big" 8 | 9 | "github.com/celo-org/celo-blockchain/common" 10 | relaychain "github.com/keep-network/keep-core/pkg/beacon/relay/chain" 11 | "github.com/keep-network/keep-core/pkg/chain" 12 | ) 13 | 14 | type celoStakeMonitor struct { 15 | celo *celoChain 16 | } 17 | 18 | func (esm *celoStakeMonitor) HasMinimumStake(address string) (bool, error) { 19 | if !common.IsHexAddress(address) { 20 | return false, fmt.Errorf("not a valid celo address: %v", address) 21 | } 22 | 23 | return esm.celo.hasMinimumStake(common.HexToAddress(address)) 24 | } 25 | 26 | func (esm *celoStakeMonitor) StakerFor(address string) (chain.Staker, error) { 27 | if !common.IsHexAddress(address) { 28 | return nil, fmt.Errorf("not a valid celo address: %v", address) 29 | } 30 | 31 | return &celoStaker{ 32 | address: address, 33 | celo: esm.celo, 34 | }, nil 35 | } 36 | 37 | // StakeMonitor generates a new `chain.StakeMonitor` from the chain 38 | func (cc *celoChain) StakeMonitor() (chain.StakeMonitor, error) { 39 | return &celoStakeMonitor{cc}, nil 40 | } 41 | 42 | type celoStaker struct { 43 | address string 44 | celo *celoChain 45 | } 46 | 47 | func (es *celoStaker) Address() relaychain.StakerAddress { 48 | return common.HexToAddress(es.address).Bytes() 49 | } 50 | 51 | func (es *celoStaker) Stake() (*big.Int, error) { 52 | return es.celo.balanceOf(common.HexToAddress(es.address)) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/chain/config.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | // Config stores configuration of application extension responsible for 4 | // executing signer actions specific for TBTC application. 5 | type Config struct { 6 | TBTCSystem string 7 | } 8 | -------------------------------------------------------------------------------- /pkg/chain/ethereum/balance_monitoring.go: -------------------------------------------------------------------------------- 1 | //+build !celo 2 | 3 | package ethereum 4 | 5 | import ( 6 | "context" 7 | "math/big" 8 | "time" 9 | 10 | "github.com/keep-network/keep-common/pkg/chain/ethereum" 11 | ) 12 | 13 | // Values related with balance monitoring. 14 | // 15 | // defaultBalanceAlertThreshold determines the alert threshold below which 16 | // the alert should be triggered. 17 | var defaultBalanceAlertThreshold = ethereum.WrapWei( 18 | big.NewInt(500000000000000000), 19 | ) 20 | 21 | // defaultBalanceMonitoringTick determines how often the monitoring 22 | // check should be triggered. 23 | const defaultBalanceMonitoringTick = 10 * time.Minute 24 | 25 | // defaultBalanceMonitoringRetryTimeout determines the timeout for balance check 26 | // at each tick. 27 | const defaultBalanceMonitoringRetryTimeout = 5 * time.Minute 28 | 29 | func (ec *ethereumChain) initializeBalanceMonitoring( 30 | ctx context.Context, 31 | ) { 32 | balanceMonitor, err := ec.BalanceMonitor() 33 | if err != nil { 34 | logger.Errorf("error obtaining balance monitor handle [%v]", err) 35 | return 36 | } 37 | 38 | alertThreshold := defaultBalanceAlertThreshold 39 | if value := ec.config.BalanceAlertThreshold; value != nil { 40 | alertThreshold = value 41 | } 42 | 43 | balanceMonitor.Observe( 44 | ctx, 45 | ec.operatorAddress(), 46 | alertThreshold, 47 | defaultBalanceMonitoringTick, 48 | defaultBalanceMonitoringRetryTimeout, 49 | ) 50 | 51 | logger.Infof( 52 | "started balance monitoring for address [%v] "+ 53 | "with the alert threshold set to [%v]", 54 | ec.operatorAddress().Hex(), 55 | alertThreshold, 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/chain/ethereum/ids.go: -------------------------------------------------------------------------------- 1 | //+build !celo 2 | 3 | package ethereum 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/keep-network/keep-ecdsa/pkg/chain" 10 | ) 11 | 12 | func (ec ethereumChain) UnmarshalID(idString string) (chain.ID, error) { 13 | if !common.IsHexAddress(idString) { 14 | return nil, fmt.Errorf( 15 | "[%v] is not a valid ethereum ID", 16 | idString, 17 | ) 18 | } 19 | 20 | return ethereumChainID(common.HexToAddress(idString)), nil 21 | } 22 | 23 | // ethereumChainID is the Ethereum-speecific chain.ID type; it is an alias for 24 | // go-ethereum/common.Address. 25 | type ethereumChainID common.Address 26 | 27 | func (eci ethereumChainID) ChainName() string { 28 | return "ethereum" 29 | } 30 | 31 | func (eci ethereumChainID) String() string { 32 | return common.Address(eci).Hex() 33 | } 34 | 35 | func (eci ethereumChainID) IsForChain(handle chain.Handle) bool { 36 | _, ok := handle.(*ethereumChain) 37 | 38 | return ok 39 | } 40 | 41 | func toIDSlice(addresses []common.Address) []chain.ID { 42 | memberIDs := make([]chain.ID, 0, len(addresses)) 43 | for _, address := range addresses { 44 | memberIDs = append(memberIDs, ethereumChainID(address)) 45 | } 46 | 47 | return memberIDs 48 | } 49 | 50 | func fromChainID(id chain.ID) (common.Address, error) { 51 | eci, ok := id.(ethereumChainID) 52 | if !ok { 53 | return common.Address{}, fmt.Errorf("failed to convert to ethereumChainID") 54 | } 55 | 56 | return common.Address(eci), nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/chain/ethereum/stake_monitor.go: -------------------------------------------------------------------------------- 1 | //+build !celo 2 | 3 | package ethereum 4 | 5 | import ( 6 | "fmt" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | relaychain "github.com/keep-network/keep-core/pkg/beacon/relay/chain" 11 | "github.com/keep-network/keep-core/pkg/chain" 12 | ) 13 | 14 | type ethereumStakeMonitor struct { 15 | ethereum *ethereumChain 16 | } 17 | 18 | func (esm *ethereumStakeMonitor) HasMinimumStake(address string) (bool, error) { 19 | if !common.IsHexAddress(address) { 20 | return false, fmt.Errorf("not a valid ethereum address: %v", address) 21 | } 22 | 23 | return esm.ethereum.hasMinimumStake(common.HexToAddress(address)) 24 | } 25 | 26 | func (esm *ethereumStakeMonitor) StakerFor(address string) (chain.Staker, error) { 27 | if !common.IsHexAddress(address) { 28 | return nil, fmt.Errorf("not a valid ethereum address: %v", address) 29 | } 30 | 31 | return ðereumStaker{ 32 | address: address, 33 | ethereum: esm.ethereum, 34 | }, nil 35 | } 36 | 37 | // StakeMonitor generates a new `chain.StakeMonitor` from the chain 38 | func (ec *ethereumChain) StakeMonitor() (chain.StakeMonitor, error) { 39 | return ðereumStakeMonitor{ec}, nil 40 | } 41 | 42 | type ethereumStaker struct { 43 | address string 44 | ethereum *ethereumChain 45 | } 46 | 47 | func (es *ethereumStaker) Address() relaychain.StakerAddress { 48 | return common.HexToAddress(es.address).Bytes() 49 | } 50 | 51 | func (es *ethereumStaker) Stake() (*big.Int, error) { 52 | return es.ethereum.balanceOf(common.HexToAddress(es.address)) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/chain/event.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | // BondedECDSAKeepCreatedEvent is an event emitted on a new keep creation. 4 | type BondedECDSAKeepCreatedEvent struct { 5 | Keep BondedECDSAKeepHandle 6 | MemberIDs []ID // keep member ids 7 | HonestThreshold uint64 8 | BlockNumber uint64 9 | ThisOperatorIsMember bool 10 | } 11 | 12 | // ConflictingPublicKeySubmittedEvent is an event emitted each time when one of 13 | // the members of a keep has submitted a key that does not match the keys submitted 14 | // so far by other members. 15 | type ConflictingPublicKeySubmittedEvent struct { 16 | SubmittingMember ID 17 | ConflictingPublicKey []byte 18 | BlockNumber uint64 19 | } 20 | 21 | // PublicKeyPublishedEvent is an event emitted once all the members have submitted 22 | // the same public key and it was accepted by keep as its public key. 23 | type PublicKeyPublishedEvent struct { 24 | PublicKey []byte 25 | BlockNumber uint64 26 | } 27 | 28 | // SignatureRequestedEvent is an event emitted when a user requests 29 | // a digest to be signed. 30 | type SignatureRequestedEvent struct { 31 | Digest [32]byte 32 | BlockNumber uint64 33 | } 34 | 35 | // KeepClosedEvent is an event emitted when a keep has been closed. 36 | type KeepClosedEvent struct { 37 | BlockNumber uint64 38 | } 39 | 40 | // KeepTerminatedEvent is an event emitted when a keep has been terminated. 41 | type KeepTerminatedEvent struct { 42 | BlockNumber uint64 43 | } 44 | 45 | // SignatureSubmittedEvent is an event emitted when a keep submits a signature. 46 | type SignatureSubmittedEvent struct { 47 | Digest [32]byte 48 | R [32]byte 49 | S [32]byte 50 | RecoveryID uint8 51 | BlockNumber uint64 52 | } 53 | -------------------------------------------------------------------------------- /pkg/chain/gen/celo/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/urfave/cli" 5 | ) 6 | 7 | // AvailableCommands is the exported list of generated commands that can be 8 | // installed on a CLI app. Generated contract command files set up init 9 | // functions that add the contract's command and subcommands to this global 10 | // variable, and any top-level command that wishes to include these commands can 11 | // reference this variable and expect it to contain all generated contract 12 | // commands. 13 | var AvailableCommands []cli.Command 14 | -------------------------------------------------------------------------------- /pkg/chain/gen/celo/contract/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keep-network/keep-ecdsa/1a4a46272b827896a7ae518bdfe4b825d8d5d0f3/pkg/chain/gen/celo/contract/.keep -------------------------------------------------------------------------------- /pkg/chain/gen/celo/gen.go: -------------------------------------------------------------------------------- 1 | package celo 2 | 3 | //go:generate sh -c "SOLIDITY_DIR=../../../../solidity make" 4 | -------------------------------------------------------------------------------- /pkg/chain/gen/ethereum/Makefile: -------------------------------------------------------------------------------- 1 | # Environment provides the solidity directory as a potentially-relative path, 2 | # which we resolve. Then we resolve the Solidity files in a contracts/ directory 3 | # at that path. 4 | solidity_dir=$(realpath ${SOLIDITY_DIR}) 5 | solidity_files := $(wildcard ${solidity_dir}/contracts/*.sol) 6 | 7 | # Bare Solidity filenames without .sol or Solidity directory prefix. 8 | contract_stems := $(notdir $(basename $(solidity_files))) 9 | # *ImplV1.go files will get generated into clean Keep contract bindings, the 10 | # corresponding contract filenames will drop the ImplV1, if it exists, and live 11 | # in the contract/ directory. 12 | clean_contract_stems := $(filter %ImplV1,$(contract_stems)) $(filter BondedECDSAKeepFactory, $(contract_stems)) $(filter BondedECDSAKeep, $(contract_stems)) 13 | contract_files := $(addprefix contract/,$(addsuffix .go,$(subst ImplV1,,$(clean_contract_stems)))) 14 | # Go abigen bindings in abi/ subdirectory with .go suffix, alongside solc ABI 15 | # files with .abi suffix. 16 | abi_files := $(addprefix abi/,$(addsuffix .abi,$(clean_contract_stems))) 17 | abigen_files := $(addprefix abi/,$(addsuffix .go,$(clean_contract_stems))) 18 | 19 | all: gen_contract_go gen_abi_go 20 | 21 | clean: 22 | rm -r abi/* 23 | rm -r contract/* 24 | mkdir tmp && mv cmd/cmd*.go tmp 25 | rm -r cmd/* 26 | mv tmp/* cmd && rm -r tmp 27 | 28 | gen_abi_go: $(abigen_files) 29 | 30 | gen_contract_go: $(contract_files) 31 | 32 | abi/%.abi: ${solidity_dir}/contracts/%.sol 33 | solc solidity-bytes-utils/=${solidity_dir}/node_modules/solidity-bytes-utils/ \ 34 | openzeppelin-solidity/=${solidity_dir}/node_modules/openzeppelin-solidity/ \ 35 | @openzeppelin/upgrades/=${solidity_dir}/node_modules/@openzeppelin/upgrades/ \ 36 | @keep-network/keep-core/=${solidity_dir}/node_modules/@keep-network/keep-core/ \ 37 | @keep-network/sortition-pools/=${solidity_dir}/node_modules/@keep-network/sortition-pools/ \ 38 | --allow-paths ${solidity_dir} \ 39 | --overwrite \ 40 | --abi \ 41 | -o abi $< 42 | 43 | 44 | abi/%.go: abi/%.abi 45 | go run github.com/ethereum/go-ethereum/cmd/abigen --abi $< --pkg abi --type $* --out $@ 46 | 47 | contract/%.go cmd/%.go: abi/%ImplV1.abi abi/%ImplV1.go abi/%.go *.go 48 | go run github.com/keep-network/keep-common/tools/generators/ethlike $< contract/$*.go cmd/$*.go 49 | 50 | contract/BondedECDSAKeepFactory.go cmd/BondedECDSAKeepFactory.go: abi/BondedECDSAKeepFactory.abi abi/BondedECDSAKeepFactory.go *.go 51 | go run github.com/keep-network/keep-common/tools/generators/ethlike $< contract/BondedECDSAKeepFactory.go cmd/BondedECDSAKeepFactory.go 52 | 53 | contract/BondedECDSAKeep.go cmd/BondedECDSAKeep.go: abi/BondedECDSAKeep.abi abi/BondedECDSAKeep.go *.go 54 | go run github.com/keep-network/keep-common/tools/generators/ethlike $< contract/BondedECDSAKeep.go cmd/BondedECDSAKeep.go 55 | -------------------------------------------------------------------------------- /pkg/chain/gen/ethereum/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/urfave/cli" 5 | ) 6 | 7 | // AvailableCommands is the exported list of generated commands that can be 8 | // installed on a CLI app. Generated contract command files set up init 9 | // functions that add the contract's command and subcommands to this global 10 | // variable, and any top-level command that wishes to include these commands can 11 | // reference this variable and expect it to contain all generated contract 12 | // commands. 13 | var AvailableCommands []cli.Command 14 | -------------------------------------------------------------------------------- /pkg/chain/gen/ethereum/contract/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keep-network/keep-ecdsa/1a4a46272b827896a7ae518bdfe4b825d8d5d0f3/pkg/chain/gen/ethereum/contract/.keep -------------------------------------------------------------------------------- /pkg/chain/gen/ethereum/gen.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | //go:generate sh -c "SOLIDITY_DIR=../../../../solidity make" 4 | -------------------------------------------------------------------------------- /pkg/chain/key.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | cecdsa "crypto/ecdsa" 5 | 6 | "github.com/keep-network/keep-ecdsa/pkg/utils/byteutils" 7 | ) 8 | 9 | // SerializePublicKey takes X and Y coordinates of a signer's public key and 10 | // concatenates it to a 64-byte long array. If any of coordinates is shorter 11 | // than 32-byte it is preceded with zeros. 12 | func SerializePublicKey(publicKey *cecdsa.PublicKey) ([64]byte, error) { 13 | var serialized [64]byte 14 | 15 | x, err := byteutils.LeftPadTo32Bytes(publicKey.X.Bytes()) 16 | if err != nil { 17 | return serialized, err 18 | } 19 | 20 | y, err := byteutils.LeftPadTo32Bytes(publicKey.Y.Bytes()) 21 | if err != nil { 22 | return serialized, err 23 | } 24 | 25 | serializedBytes := append(x, y...) 26 | 27 | copy(serialized[:], serializedBytes) 28 | 29 | return serialized, nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/chain/key_test.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | "bytes" 5 | "math/big" 6 | "testing" 7 | 8 | cecdsa "crypto/ecdsa" 9 | ) 10 | 11 | func TestSerializePublicKey(t *testing.T) { 12 | bytes32 := []byte{207, 73, 229, 19, 136, 216, 125, 157, 135, 142, 67, 130, 13 | 136, 13, 76, 188, 32, 218, 243, 134, 95, 73, 155, 24, 38, 73, 117, 90, 14 | 215, 95, 216, 19} 15 | bytes31 := []byte{182, 142, 176, 51, 131, 130, 111, 197, 191, 103, 180, 137, 16 | 171, 101, 34, 78, 251, 234, 118, 184, 16, 116, 238, 82, 131, 153, 134, 17 | 17, 46, 158, 94} 18 | 19 | expectedResult := [64]byte{ 20 | // bytes32 21 | 207, 73, 229, 19, 136, 216, 125, 157, 135, 142, 67, 130, 136, 13, 76, 22 | 188, 32, 218, 243, 134, 95, 73, 155, 24, 38, 73, 117, 90, 215, 95, 216, 23 | 19, 24 | // padding 25 | 00, 26 | // bytes31 27 | 182, 142, 176, 51, 131, 130, 111, 197, 191, 103, 180, 137, 171, 101, 34, 28 | 78, 251, 234, 118, 184, 16, 116, 238, 82, 131, 153, 134, 17, 46, 158, 94, 29 | } 30 | 31 | actualResult, err := SerializePublicKey( 32 | &cecdsa.PublicKey{ 33 | X: new(big.Int).SetBytes(bytes32), 34 | Y: new(big.Int).SetBytes(bytes31), 35 | }, 36 | ) 37 | 38 | if !bytes.Equal(expectedResult[:], actualResult[:]) { 39 | t.Errorf( 40 | "unexpected result\nexpected: [%+v]\nactual: [%+v]", 41 | expectedResult, 42 | actualResult, 43 | ) 44 | } 45 | 46 | if err != nil { 47 | t.Errorf("unexpected error [%+v]", err) 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/chain/local/bonded_ecdsa_keep_factory.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/keep-network/keep-ecdsa/pkg/chain" 8 | ) 9 | 10 | func (c *localChain) createKeep( 11 | keepAddress common.Address, 12 | ) error { 13 | return c.createKeepWithMembers(keepAddress, keepAddress, []common.Address{}) 14 | } 15 | 16 | func (c *localChain) createKeepWithMembers( 17 | keepAddress common.Address, 18 | ownerAddress common.Address, 19 | members []common.Address, 20 | ) error { 21 | c.localChainMutex.Lock() 22 | defer c.localChainMutex.Unlock() 23 | 24 | if _, ok := c.keeps[keepAddress]; ok { 25 | return fmt.Errorf( 26 | "keep already exists for address [%s]", 27 | keepAddress.String(), 28 | ) 29 | } 30 | 31 | localKeep := &localKeep{ 32 | chain: c, 33 | keepID: keepAddress, 34 | owner: ownerAddress, 35 | publicKey: [64]byte{}, 36 | members: members, 37 | signatureRequestedHandlers: make(map[int]func(event *chain.SignatureRequestedEvent)), 38 | keepClosedHandlers: make(map[int]func(event *chain.KeepClosedEvent)), 39 | keepTerminatedHandlers: make(map[int]func(event *chain.KeepTerminatedEvent)), 40 | signatureSubmittedEvents: make([]*chain.SignatureSubmittedEvent, 0), 41 | } 42 | 43 | c.keeps[keepAddress] = localKeep 44 | c.keepAddresses = append(c.keepAddresses, keepAddress) 45 | 46 | // Ignore errors as the local version never errors. 47 | operatorIndex := localKeep.unsafeOperatorIndex() 48 | 49 | keepCreatedEvent := &chain.BondedECDSAKeepCreatedEvent{ 50 | Keep: localKeep, 51 | ThisOperatorIsMember: operatorIndex > -1, 52 | } 53 | 54 | for _, handler := range c.keepCreatedHandlers { 55 | go func( 56 | handler func(event *chain.BondedECDSAKeepCreatedEvent), 57 | keepCreatedEvent *chain.BondedECDSAKeepCreatedEvent, 58 | ) { 59 | handler(keepCreatedEvent) 60 | }(handler, keepCreatedEvent) 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/chain/local/bonded_ecdsa_keep_factory_test.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | ) 12 | 13 | func TestCreateKeepDuplicate(t *testing.T) { 14 | ctx, cancelCtx := context.WithCancel(context.Background()) 15 | defer cancelCtx() 16 | 17 | chain := initializeLocalChain(ctx) 18 | keepAddress := common.Address([20]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) 19 | expectedError := fmt.Errorf("keep already exists for address [0x0000000000000000000000000000000000000001]") 20 | 21 | err := chain.createKeep(keepAddress) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | err = chain.createKeep(keepAddress) 27 | if !reflect.DeepEqual(err, expectedError) { 28 | t.Fatalf( 29 | "unexpected error\nexpected: [%v]\nactual: [%v]", 30 | expectedError, 31 | err.Error(), 32 | ) 33 | } 34 | } 35 | 36 | func TestCreateKeep(t *testing.T) { 37 | ctx, cancelCtx := context.WithCancel(context.Background()) 38 | defer cancelCtx() 39 | 40 | chain := initializeLocalChain(ctx) 41 | keepAddress := common.Address([20]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) 42 | expectedPublicKey := [64]byte{} 43 | 44 | err := chain.createKeep(keepAddress) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | keep, ok := chain.keeps[keepAddress] 50 | if !ok { 51 | t.Fatal("keep not found after creation") 52 | } 53 | 54 | if !bytes.Equal(keep.publicKey[:], expectedPublicKey[:]) { 55 | t.Errorf( 56 | "unexpected publicKey value for keep\nexpected: %x\nactual: %x\n", 57 | expectedPublicKey, 58 | keep.publicKey, 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/chain/local/bonded_ecdsa_keep_test.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "math/rand" 8 | "reflect" 9 | "testing" 10 | "time" 11 | 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/keep-network/keep-ecdsa/pkg/chain" 14 | ) 15 | 16 | var emptyAddress = common.BytesToAddress([]byte{}) 17 | 18 | func TestRequestSignatureNonexistentKeep(t *testing.T) { 19 | ctx, cancelCtx := context.WithCancel(context.Background()) 20 | defer cancelCtx() 21 | 22 | localChain := initializeLocalChain(ctx) 23 | keepAddress := common.Address([20]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) 24 | digest := [32]byte{1} 25 | expectedError := fmt.Errorf("failed to find keep with address: [0x0000000000000000000000000000000000000001]") 26 | 27 | err := localChain.RequestSignature(keepAddress, digest) 28 | 29 | if !reflect.DeepEqual(err, expectedError) { 30 | t.Fatalf( 31 | "unexpected error\nexpected: [%v]\nactual: [%v]", 32 | expectedError, 33 | err.Error(), 34 | ) 35 | } 36 | } 37 | 38 | func TestRequestSignatureNoHandler(t *testing.T) { 39 | ctx, cancelCtx := context.WithCancel(context.Background()) 40 | defer cancelCtx() 41 | 42 | localChain := initializeLocalChain(ctx) 43 | keepAddress := common.Address([20]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) 44 | digest := [32]byte{1} 45 | 46 | keep := localChain.OpenKeep(keepAddress, emptyAddress, []common.Address{}) 47 | 48 | var keepPubkey [64]byte 49 | rand.Read(keepPubkey[:]) 50 | 51 | err := keep.SubmitKeepPublicKey(keepPubkey) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | err = localChain.RequestSignature(keepAddress, digest) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | } 61 | 62 | func TestRequestSignature(t *testing.T) { 63 | ctx, cancelCtx := context.WithTimeout(context.Background(), 1*time.Second) 64 | defer cancelCtx() 65 | 66 | localChain := initializeLocalChain(ctx) 67 | keepAddress := common.Address([20]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) 68 | digest := [32]byte{1} 69 | eventEmitted := make(chan *chain.SignatureRequestedEvent) 70 | handler := func(event *chain.SignatureRequestedEvent) { 71 | eventEmitted <- event 72 | } 73 | 74 | keep := localChain.OpenKeep(keepAddress, emptyAddress, []common.Address{}) 75 | 76 | var keepPubkey [64]byte 77 | rand.Read(keepPubkey[:]) 78 | 79 | err := keep.SubmitKeepPublicKey(keepPubkey) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | localChain.keeps[keepAddress].signatureRequestedHandlers[0] = handler 85 | 86 | err = localChain.RequestSignature(keepAddress, digest) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | select { 92 | case event := <-eventEmitted: 93 | if !bytes.Equal(event.Digest[:], digest[:]) { 94 | t.Errorf( 95 | "unexpected digest from signature request\nexpected: %x\nactual: %x\n", 96 | digest, 97 | event.Digest, 98 | ) 99 | } 100 | case <-ctx.Done(): 101 | t.Fatal(ctx.Err()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pkg/chain/local/ids.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | cecdsa "crypto/ecdsa" 5 | "fmt" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/keep-network/keep-core/pkg/operator" 9 | "github.com/keep-network/keep-ecdsa/pkg/chain" 10 | ) 11 | 12 | func (lc *localChain) UnmarshalID(idString string) (chain.ID, error) { 13 | if !common.IsHexAddress(idString) { 14 | return nil, fmt.Errorf( 15 | "[%v] is not a valid ethereum ID", 16 | idString, 17 | ) 18 | } 19 | 20 | return localChainID(common.HexToAddress(idString)), nil 21 | } 22 | 23 | func (lc *localChain) PublicKeyToOperatorID(publicKey *cecdsa.PublicKey) chain.ID { 24 | return localChainID(operator.PubkeyToAddress(*publicKey)) 25 | } 26 | 27 | // localChainID is the local chain.ID type; it is an alias for 28 | // go-ethereum/common.Address. 29 | type localChainID common.Address 30 | 31 | func (ci localChainID) ChainName() string { 32 | return "local" 33 | } 34 | 35 | func (ci localChainID) String() string { 36 | return common.Address(ci).Hex() 37 | } 38 | 39 | func (ci localChainID) IsForChain(handle chain.Handle) bool { 40 | _, ok := handle.(*localChain) 41 | 42 | return ok 43 | } 44 | 45 | func toIDSlice(addresses []common.Address) []chain.ID { 46 | memberIDs := make([]chain.ID, 0, len(addresses)) 47 | for _, address := range addresses { 48 | memberIDs = append(memberIDs, localChainID(address)) 49 | } 50 | 51 | return memberIDs 52 | } 53 | 54 | func fromChainID(id chain.ID) (common.Address, error) { 55 | ci, ok := id.(localChainID) 56 | if !ok { 57 | return common.Address{}, fmt.Errorf("failed to convert to localChainID") 58 | } 59 | 60 | return common.Address(ci), nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/chain/local/tbtc_test.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/keep-network/keep-ecdsa/pkg/chain" 10 | ) 11 | 12 | const ( 13 | depositAddress = "0xa5FA806723A7c7c8523F33c39686f20b52612877" 14 | ) 15 | 16 | func TestFundingInfo(t *testing.T) { 17 | expectedFundingInfo := &chain.FundingInfo{ 18 | UtxoValueBytes: [8]uint8{128, 150, 152, 0, 0, 0, 0, 0}, 19 | FundedAt: big.NewInt(1615172517), 20 | TransactionHash: "c27c3bfa8293ac6b303b9f7455ae23b7c24b8814915a6511976027064efc4d51", 21 | OutputIndex: 1, 22 | } 23 | 24 | ctx, cancelCtx := context.WithCancel(context.Background()) 25 | defer cancelCtx() 26 | 27 | tbtcChain := NewTBTCLocalChain(ctx) 28 | 29 | tbtcChain.CreateDeposit(depositAddress, RandomSigningGroup(3)) 30 | tbtcChain.FundDeposit(depositAddress) 31 | 32 | fundingInfo, err := tbtcChain.FundingInfo(depositAddress) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | if !reflect.DeepEqual(expectedFundingInfo, fundingInfo) { 38 | t.Errorf( 39 | "funding info does not match\nexpected: %+v\nactual: %+v", 40 | expectedFundingInfo, 41 | fundingInfo, 42 | ) 43 | } 44 | } 45 | 46 | func TestFundingInfo_NotFunded(t *testing.T) { 47 | ctx, cancelCtx := context.WithCancel(context.Background()) 48 | defer cancelCtx() 49 | 50 | tbtcChain := NewTBTCLocalChain(ctx) 51 | 52 | tbtcChain.CreateDeposit(depositAddress, RandomSigningGroup(3)) 53 | 54 | fundingInfo, err := tbtcChain.FundingInfo(depositAddress) 55 | if err != chain.ErrDepositNotFunded { 56 | t.Errorf( 57 | "unexpected error\nexpected: %v\nactual: %v", 58 | chain.ErrDepositNotFunded, 59 | err, 60 | ) 61 | } 62 | 63 | if fundingInfo != nil { 64 | t.Errorf( 65 | "funding info does not match\nexpected: %+v\nactual: %+v", 66 | nil, 67 | fundingInfo, 68 | ) 69 | } 70 | } 71 | 72 | func TestGetOwner(t *testing.T) { 73 | ctx, cancelCtx := context.WithCancel(context.Background()) 74 | defer cancelCtx() 75 | 76 | tbtcChain := NewTBTCLocalChain(ctx) 77 | 78 | signers := RandomSigningGroup(3) 79 | 80 | tbtcChain.CreateDeposit(depositAddress, signers) 81 | keep, err := tbtcChain.Keep(depositAddress) 82 | if err != nil { 83 | t.Fatalf("unexpected error %v", err) 84 | } 85 | owner, err := keep.GetOwner() 86 | if err != nil { 87 | t.Fatalf("unexpected error %v", err) 88 | } 89 | if owner.String() != depositAddress { 90 | t.Errorf( 91 | "unexpected owner address\nexpected: %s\nactual: %s", 92 | depositAddress, 93 | owner.String(), 94 | ) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkg/client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "time" 5 | 6 | configtime "github.com/keep-network/keep-ecdsa/config/time" 7 | ) 8 | 9 | const ( 10 | // The default look-back period to check if existing, active keeps are awaiting 11 | // signer generation. When the client starts, it goes through all keeps 12 | // registered on-chain to check whether it's a member of one of them and to 13 | // generate the signing key if needed. The client does not check keeps older 14 | // than the awaiting key generation lookback value allows to minimize the 15 | // number of calls to the chain calls. 16 | defaultAwaitingKeyGenerationLookback = 24 * time.Hour 17 | 18 | // The default value of a timeout for a keep key generation. 19 | defaultKeyGenerationTimeout = 3 * time.Hour 20 | 21 | // The default value of a timeout for a signature calculation. 22 | defaultSigningTimeout = 2 * time.Hour 23 | ) 24 | 25 | // Config contains configuration for tss protocol execution. 26 | type Config struct { 27 | // Defines the look-back period to check if existing, active keeps are awaiting 28 | // signer generation on the client start. The client does not check keeps older 29 | // than the look-back value. 30 | AwaitingKeyGenerationLookback configtime.Duration 31 | 32 | // Timeout for key generation and signature calculation. 33 | KeyGenerationTimeout configtime.Duration 34 | SigningTimeout configtime.Duration 35 | } 36 | 37 | // GetAwaitingKeyGenerationLookback returns a look-back period to check if 38 | // existing, active keeps are awaiting signer generation. If a value is not set 39 | // it returns a default value. 40 | func (c *Config) GetAwaitingKeyGenerationLookback() time.Duration { 41 | lookbackPeriod := c.AwaitingKeyGenerationLookback.ToDuration() 42 | if lookbackPeriod == 0 { 43 | lookbackPeriod = defaultAwaitingKeyGenerationLookback 44 | } 45 | 46 | return lookbackPeriod 47 | } 48 | 49 | // GetKeyGenerationTimeout returns key generation timeout. If a value is not set 50 | // it returns a default value. 51 | func (c *Config) GetKeyGenerationTimeout() time.Duration { 52 | timeout := c.KeyGenerationTimeout.ToDuration() 53 | if timeout == 0 { 54 | timeout = defaultKeyGenerationTimeout 55 | } 56 | 57 | return timeout 58 | } 59 | 60 | // GetSigningTimeout returns signature calculation timeout. If a value is not set 61 | // it returns a default value. 62 | func (c *Config) GetSigningTimeout() time.Duration { 63 | timeout := c.SigningTimeout.ToDuration() 64 | if timeout == 0 { 65 | timeout = defaultSigningTimeout 66 | } 67 | 68 | return timeout 69 | } 70 | -------------------------------------------------------------------------------- /pkg/ecdsa/ecdsa.go: -------------------------------------------------------------------------------- 1 | // Package ecdsa defines interfaces for ECDSA signing based on [SEC 1]. 2 | // 3 | // [SEC 1]: Standards for Efficient Cryptography, SEC 1: Elliptic Curve 4 | // Cryptography, Certicom Research, https://www.secg.org/sec1-v2.pdf 5 | package ecdsa 6 | 7 | import ( 8 | "fmt" 9 | "math/big" 10 | ) 11 | 12 | // Signature holds a signature in a form of two big.Int `r` and `s` values and a 13 | // recovery ID value in {0, 1, 2, 3}. 14 | // 15 | // The signature is chain-agnostic. Some chains (e.g. Ethereum and BTC) requires 16 | // `v` to start from 27. Please consult the documentation about what the 17 | // particular chain expects. 18 | type Signature struct { 19 | R *big.Int 20 | S *big.Int 21 | RecoveryID int 22 | } 23 | 24 | // String formats Signature to a string that contains R and S values as hexadecimals. 25 | func (s *Signature) String() string { 26 | return fmt.Sprintf("R: %#x, S: %#x, RecoveryID: %d", s.R, s.S, s.RecoveryID) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/ecdsa/ecdsa_test.go: -------------------------------------------------------------------------------- 1 | package ecdsa 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | ) 7 | 8 | func TestSignatureString(t *testing.T) { 9 | signature := &Signature{ 10 | R: big.NewInt(1234567890), 11 | S: big.NewInt(963852741), 12 | RecoveryID: 1, 13 | } 14 | expectedString := "R: 0x499602d2, S: 0x397339c5, RecoveryID: 1" 15 | 16 | if signature.String() != expectedString { 17 | t.Errorf( 18 | "unexpected signature.String() result\n"+ 19 | "expected: [%s]\n"+ 20 | "actual: [%s]", 21 | expectedString, 22 | signature.String(), 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/config.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import ( 4 | "time" 5 | 6 | configtime "github.com/keep-network/keep-ecdsa/config/time" 7 | ) 8 | 9 | const ( 10 | defaultPreParamsGenerationTimeout = 2 * time.Minute 11 | defaultPreParamsTargetPoolSize = 20 12 | ) 13 | 14 | // Config contains configuration for tss protocol execution. 15 | type Config struct { 16 | // Timeout for pre-parameters generation in tss-lib. 17 | PreParamsGenerationTimeout configtime.Duration 18 | 19 | // Target size of the TSS pre params pool. 20 | PreParamsTargetPoolSize int 21 | } 22 | 23 | // GetPreParamsGenerationTimeout returns pre-parameters generation timeout. If 24 | // a value is not set it returns a default value. 25 | func (c *Config) GetPreParamsGenerationTimeout() time.Duration { 26 | timeout := c.PreParamsGenerationTimeout.ToDuration() 27 | if timeout == 0 { 28 | timeout = defaultPreParamsGenerationTimeout 29 | } 30 | 31 | return timeout 32 | } 33 | 34 | // GetPreParamsTargetPoolSize returns the pre-parameters target pool size. If 35 | // a value is not set it returns a default value. 36 | func (c *Config) GetPreParamsTargetPoolSize() int { 37 | poolSize := c.PreParamsTargetPoolSize 38 | if poolSize == 0 { 39 | poolSize = defaultPreParamsTargetPoolSize 40 | } 41 | 42 | return poolSize 43 | } 44 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/error.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | type timeoutError struct { 10 | timeout time.Duration 11 | stage string 12 | memberIDs []MemberID 13 | } 14 | 15 | func (t timeoutError) Error() string { 16 | if len(t.memberIDs) > 0 { 17 | stringIDs := []string{} 18 | 19 | for _, memberID := range t.memberIDs { 20 | stringIDs = append(stringIDs, memberID.String()) 21 | } 22 | 23 | return fmt.Sprintf( 24 | "timeout [%s] exceeded on stage [%s] - still waiting for members: [%s]", 25 | t.timeout, 26 | t.stage, 27 | strings.Join(stringIDs, ", "), 28 | ) 29 | } 30 | 31 | return fmt.Sprintf( 32 | "timeout [%s] exceeded on stage [%s]", 33 | t.timeout, 34 | t.stage, 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/gen/gen.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | //go:generate sh -c "protoc --proto_path=$GOPATH/src:. --gogoslick_out=. */*.proto" 4 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/gen/pb/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "pb"; 4 | package tss; 5 | 6 | message TSSProtocolMessage { 7 | bytes senderID = 1; 8 | bytes payload = 2; 9 | bool isBroadcast = 3; 10 | string sessionID = 4; 11 | } 12 | 13 | message ReadyMessage { 14 | bytes senderID = 1; 15 | } 16 | 17 | message AnnounceMessage { 18 | bytes senderID = 1; 19 | } 20 | 21 | message LiquidationRecoveryAnnounceMessage { 22 | bytes senderID = 1; 23 | string btcRecoveryAddress = 2; 24 | int32 maxFeePerVByte = 3; 25 | } 26 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/gen/pb/signer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "pb"; 4 | package tss; 5 | 6 | message ThresholdSigner { 7 | message GroupInfo { 8 | string groupID = 1; 9 | bytes memberID = 2; 10 | repeated bytes groupMemberIDs = 3; 11 | int32 dishonestThreshold = 4; 12 | } 13 | 14 | GroupInfo groupInfo = 1; 15 | bytes thresholdKey = 2; 16 | } 17 | 18 | message LocalPartySaveData { 19 | message LocalPreParams { 20 | message PrivateKey { 21 | bytes publicKey = 1; 22 | bytes lambdaN = 2; 23 | bytes phiN = 3; 24 | } 25 | 26 | PrivateKey paillierSK = 1; 27 | bytes nTilde = 2; 28 | bytes h1i = 3; 29 | bytes h2i = 4; 30 | bytes alpha = 5; 31 | bytes beta = 6; 32 | bytes p = 7; 33 | bytes q = 8; 34 | } 35 | 36 | message LocalSecrets { 37 | bytes xi = 1; 38 | bytes shareID = 2; 39 | } 40 | 41 | message ECPoint { 42 | bytes x = 1; 43 | bytes y = 2; 44 | } 45 | 46 | LocalPreParams localPreParams = 1; 47 | LocalSecrets localSecrets = 2; 48 | repeated bytes ks = 3; 49 | repeated bytes nTildej = 4; 50 | repeated bytes h1j = 5; 51 | repeated bytes h2j = 6; 52 | repeated ECPoint bigXj = 7; 53 | repeated bytes paillierPKs = 8; 54 | ECPoint ecdsaPub = 9; 55 | } 56 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/member.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "math/big" 7 | 8 | "github.com/keep-network/keep-core/pkg/operator" 9 | ) 10 | 11 | // MemberID is an unique identifier of a member across the network. 12 | type MemberID []byte 13 | 14 | // MemberIDFromPublicKey creates a MemberID from a public key. 15 | func MemberIDFromPublicKey(publicKey *operator.PublicKey) MemberID { 16 | return operator.Marshal(publicKey) 17 | } 18 | 19 | // PublicKey returns the MemberID as a public key. 20 | func (id MemberID) PublicKey() (*operator.PublicKey, error) { 21 | return operator.Unmarshal(id) 22 | } 23 | 24 | // MemberIDFromString creates a MemberID from a string. 25 | func MemberIDFromString(string string) (MemberID, error) { 26 | return hex.DecodeString(string) 27 | } 28 | 29 | // String converts MemberID to string. 30 | func (id MemberID) String() string { 31 | return hex.EncodeToString(id) 32 | } 33 | 34 | // bigInt converts MemberID to big.Int. 35 | func (id MemberID) bigInt() *big.Int { 36 | return new(big.Int).SetBytes(id) 37 | } 38 | 39 | // Equal checks if member IDs are equal. 40 | func (id MemberID) Equal(memberID MemberID) bool { 41 | return bytes.Equal(id, memberID) 42 | } 43 | 44 | // groupInfo holds information about the group selected for protocol execution. 45 | type groupInfo struct { 46 | groupID string // globally unique group identifier 47 | memberID MemberID 48 | groupMemberIDs []MemberID 49 | // Dishonest threshold `t` defines a maximum number of signers controlled by the 50 | // adversary such that the adversary still cannot produce a signature. Any subset 51 | // of `t + 1` players can jointly sign, but any smaller subset cannot. 52 | dishonestThreshold int 53 | } 54 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/member_test.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/keep-network/keep-core/pkg/operator" 7 | ) 8 | 9 | func TestMemberID(t *testing.T) { 10 | _, publicKey, err := operator.GenerateKeyPair() 11 | if err != nil { 12 | t.Fatalf("could not generate public key: [%v]", err) 13 | } 14 | 15 | memberID := MemberIDFromPublicKey(publicKey) 16 | 17 | extractedPublicKey, err := memberID.PublicKey() 18 | if err != nil { 19 | t.Fatalf("could not extract public key: [%v]", err) 20 | } 21 | 22 | if !MemberIDFromPublicKey(extractedPublicKey).Equal(memberID) { 23 | t.Errorf("member from extracted public key doesn't match the original member") 24 | } 25 | 26 | memberIDString := memberID.String() 27 | 28 | memberIDFromString, err := MemberIDFromString(memberIDString) 29 | if err != nil { 30 | t.Fatalf("could not construct member from string: [%v]", err) 31 | } 32 | 33 | if !memberIDFromString.Equal(memberID) { 34 | t.Errorf("member from string doesn't match the original member") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/message.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import "github.com/keep-network/keep-core/pkg/net" 4 | 5 | // ProtocolMessage is a network message used to transport messages generated in 6 | // TSS protocol execution. It is a wrapper over a message generated by underlying 7 | // implementation of the protocol. 8 | type ProtocolMessage struct { 9 | SenderID MemberID 10 | Payload []byte 11 | IsBroadcast bool 12 | SessionID string 13 | } 14 | 15 | // Type returns a string type of the `TSSMessage` so that it conforms to 16 | // `net.Message` interface. 17 | func (m *ProtocolMessage) Type() string { 18 | return "ecdsa/tss_message" 19 | } 20 | 21 | // ReadyMessage is a network message used to notify peer members about readiness 22 | // to start protocol execution. 23 | type ReadyMessage struct { 24 | SenderID MemberID 25 | } 26 | 27 | // Type returns a string type of the `ReadyMessage`. 28 | func (m *ReadyMessage) Type() string { 29 | return "ecdsa/ready_message" 30 | } 31 | 32 | // AnnounceMessage is a network message used to announce peer's presence. 33 | type AnnounceMessage struct { 34 | SenderID MemberID 35 | } 36 | 37 | // Type returns a string type of the `AnnounceMessage`. 38 | func (m *AnnounceMessage) Type() string { 39 | return "ecdsa/announce_message" 40 | } 41 | 42 | // LiquidationRecoveryAnnounceMessage is a network message used announce a BTC 43 | // recovery address to other signers on a group 44 | type LiquidationRecoveryAnnounceMessage struct { 45 | SenderID MemberID 46 | BtcRecoveryAddress string 47 | MaxFeePerVByte int32 48 | } 49 | 50 | // Type returns a string type of the `LiquidationRecoveryAnnounceMessage` so 51 | // that it conforms to `net.Message` interface 52 | func (m *LiquidationRecoveryAnnounceMessage) Type() string { 53 | return "ecdsa/liquidation_recovery_message" 54 | } 55 | 56 | // RegisterUnmarshalers is a boilerplate method to register unmarshaling on a broadcast channel 57 | func RegisterUnmarshalers(broadcastChannel net.BroadcastChannel) { 58 | broadcastChannel.SetUnmarshaler(func() net.TaggedUnmarshaler { 59 | return &AnnounceMessage{} 60 | }) 61 | 62 | broadcastChannel.SetUnmarshaler(func() net.TaggedUnmarshaler { 63 | return &ReadyMessage{} 64 | }) 65 | 66 | broadcastChannel.SetUnmarshaler(func() net.TaggedUnmarshaler { 67 | return &ProtocolMessage{} 68 | }) 69 | 70 | broadcastChannel.SetUnmarshaler(func() net.TaggedUnmarshaler { 71 | return &LiquidationRecoveryAnnounceMessage{} 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/params/params_box.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/binance-chain/tss-lib/ecdsa/keygen" 7 | ) 8 | 9 | // Box is a container for TSS key generation parameters. 10 | // Box lets to get its content as well as to destroy it. 11 | // 12 | // This type is useful for passing pre-parameters around for retried 13 | // key generation attempts. Generating pre-parameters is very expensive 14 | // and we do not want to re-generate them for every key generation retry 15 | // attempt. On the other hand, pre-parameters can't be reused once they were 16 | // used for communication with other clients. 17 | // 18 | // For this reason, instead of passing raw pre-parameters to key generation, we 19 | // can pass a box. When the node shares its pre parameters with other nodes, 20 | // box content should be destroyed. Until then, it's fine to pass the box 21 | // around and consume its content for any calculations needed between retried 22 | // key-generation attempts. 23 | type Box struct { 24 | params *keygen.LocalPreParams 25 | } 26 | 27 | // NewBox creates a new PreParamsBox with the provided key generation pre-params 28 | // inside. 29 | func NewBox(params *keygen.LocalPreParams) *Box { 30 | return &Box{ 31 | params: params, 32 | } 33 | } 34 | 35 | // Content gets the box content or error if the content has been previously 36 | // destroyed. 37 | func (b *Box) Content() (*keygen.LocalPreParams, error) { 38 | if b.IsEmpty() { 39 | return nil, fmt.Errorf("box is empty") 40 | } 41 | 42 | return b.params, nil 43 | } 44 | 45 | // IsEmpty returns true if the box content has been destroyed. 46 | // Otherwise, returns false. 47 | func (b *Box) IsEmpty() bool { 48 | return b.params == nil 49 | } 50 | 51 | // DestroyContent destroys the box content so that all further calls to 52 | // Content() function will fail. 53 | func (b *Box) DestroyContent() { 54 | b.params = nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/params/params_box_test.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/binance-chain/tss-lib/ecdsa/keygen" 10 | ) 11 | 12 | func TestGetContent(t *testing.T) { 13 | params := &keygen.LocalPreParams{ 14 | P: big.NewInt(1), 15 | Q: big.NewInt(2), 16 | } 17 | box := NewBox(params) 18 | 19 | content, err := box.Content() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | if !reflect.DeepEqual(params, content) { 25 | t.Fatalf( 26 | "unexpected content\nexpected: [%v]\nactual: [%v]", 27 | params, 28 | content, 29 | ) 30 | } 31 | } 32 | 33 | func TestGetDestroyedContent(t *testing.T) { 34 | params := &keygen.LocalPreParams{ 35 | P: big.NewInt(1), 36 | Q: big.NewInt(2), 37 | } 38 | box := NewBox(params) 39 | 40 | box.DestroyContent() 41 | _, err := box.Content() 42 | 43 | expectedError := fmt.Errorf("box is empty") 44 | if !reflect.DeepEqual(expectedError, err) { 45 | t.Fatalf( 46 | "unexpected error\nexpected: [%v]\nactual: [%v]", 47 | expectedError, 48 | err, 49 | ) 50 | } 51 | } 52 | 53 | func TestIsEmpty(t *testing.T) { 54 | params := &keygen.LocalPreParams{ 55 | P: big.NewInt(1), 56 | Q: big.NewInt(2), 57 | } 58 | box := NewBox(params) 59 | 60 | if box.IsEmpty() { 61 | t.Fatal("box should not be empty") 62 | } 63 | 64 | box.DestroyContent() 65 | 66 | if !box.IsEmpty() { 67 | t.Fatal("box should be empty") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/protocol_ready_test.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import ( 4 | "context" 5 | cecdsa "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/keep-network/keep-core/pkg/net" 12 | 13 | "github.com/ipfs/go-log" 14 | "github.com/keep-network/keep-core/pkg/net/key" 15 | ) 16 | 17 | func TestReadyProtocol(t *testing.T) { 18 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 19 | 20 | err := log.SetLogLevel("*", "INFO") 21 | if err != nil { 22 | t.Fatalf("logger initialization failed: [%v]", err) 23 | } 24 | 25 | groupSize := 5 26 | 27 | groupMembers, err := generateMemberKeys(groupSize) 28 | if err != nil { 29 | t.Fatalf("failed to generate members keys: [%v]", err) 30 | } 31 | 32 | pubKeyToAddressFn := func(publicKey cecdsa.PublicKey) []byte { 33 | return elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y) 34 | } 35 | 36 | errChan := make(chan error) 37 | 38 | waitGroup := &sync.WaitGroup{} 39 | waitGroup.Add(groupSize) 40 | 41 | mutex := &sync.RWMutex{} 42 | readyCount := 0 43 | 44 | for _, memberID := range groupMembers { 45 | go func(memberID MemberID) { 46 | groupInfo := &groupInfo{ 47 | groupID: "test-group-1", 48 | memberID: memberID, 49 | groupMemberIDs: groupMembers, 50 | } 51 | 52 | memberPublicKey, err := memberID.PublicKey() 53 | if err != nil { 54 | errChan <- err 55 | return 56 | } 57 | 58 | memberNetworkKey := key.NetworkPublic(*memberPublicKey) 59 | networkProvider := newTestNetProvider(&memberNetworkKey) 60 | 61 | broadcastChannel, err := networkProvider.BroadcastChannelFor("test-group-1") 62 | if err != nil { 63 | errChan <- err 64 | return 65 | } 66 | 67 | broadcastChannel.SetUnmarshaler(func() net.TaggedUnmarshaler { 68 | return &ReadyMessage{} 69 | }) 70 | 71 | defer waitGroup.Done() 72 | 73 | if err := readyProtocol( 74 | ctx, 75 | groupInfo, 76 | broadcastChannel, 77 | pubKeyToAddressFn, 78 | ); err != nil { 79 | errChan <- err 80 | return 81 | } 82 | 83 | mutex.Lock() 84 | readyCount++ 85 | mutex.Unlock() 86 | }(memberID) 87 | } 88 | 89 | go func() { 90 | waitGroup.Wait() 91 | cancel() 92 | }() 93 | 94 | select { 95 | case <-ctx.Done(): 96 | if readyCount != groupSize { 97 | t.Errorf( 98 | "invalid number of received notifications\nexpected: [%d]\nactual: [%d]", 99 | groupSize, 100 | readyCount, 101 | ) 102 | } 103 | case err := <-errChan: 104 | t.Fatal(err) 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/signer.go: -------------------------------------------------------------------------------- 1 | package tss 2 | 3 | import ( 4 | cecdsa "crypto/ecdsa" 5 | 6 | "github.com/binance-chain/tss-lib/ecdsa/keygen" 7 | tssLib "github.com/binance-chain/tss-lib/tss" 8 | ) 9 | 10 | // ThresholdSigner is a threshold signer who completed key generation stage. 11 | type ThresholdSigner struct { 12 | *groupInfo 13 | 14 | // thresholdKey contains a signer's key generated for a threshold signing 15 | // scheme. This data should be persisted to a local storage. 16 | thresholdKey ThresholdKey 17 | } 18 | 19 | // ThresholdKey contains data of signer's threshold key. 20 | type ThresholdKey keygen.LocalPartySaveData 21 | 22 | // MemberID returns member's unique identifer. 23 | func (s *ThresholdSigner) MemberID() MemberID { 24 | return s.memberID 25 | } 26 | 27 | // GroupID return signing group unique identifer. 28 | func (s *ThresholdSigner) GroupID() string { 29 | return s.groupID 30 | } 31 | 32 | // PublicKey returns signer's ECDSA public key which is also the signing group's 33 | // public key. 34 | func (s *ThresholdSigner) PublicKey() *cecdsa.PublicKey { 35 | pkX, pkY := s.thresholdKey.ECDSAPub.X(), s.thresholdKey.ECDSAPub.Y() 36 | 37 | curve := tssLib.EC() 38 | publicKey := cecdsa.PublicKey{ 39 | Curve: curve, 40 | X: pkX, 41 | Y: pkY, 42 | } 43 | 44 | return &publicKey 45 | } 46 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/tss_celo_test.go: -------------------------------------------------------------------------------- 1 | //+build celo 2 | 3 | package tss 4 | 5 | import ( 6 | "github.com/celo-org/celo-blockchain/crypto" 7 | "github.com/celo-org/celo-blockchain/crypto/secp256k1" 8 | ) 9 | 10 | // Components defined in this file act as a facade in front of components 11 | // provided by the `celo-blockchain/crypto` package. This way, the client code 12 | // does not have to import `celo-blockchain/crypto` directly. As result, the 13 | // backing package can be swapped to an other `celo-blockchain/crypto`-like 14 | // package at build time, via according build tags. That solution aims to 15 | // address problems with package conflicts as package `celo-blockchain/crypto` 16 | // uses some native C code underneath. If another `celo-blockchain/crypto`-like 17 | // package is used in the same time, the compilation may fail due to C linker 18 | // errors caused by duplicated symbols. Such a problem can be observed while 19 | // trying to use the `celo-blockchain/crypto` and `ethereum-go/crypto` at the 20 | // same time. 21 | 22 | var S256 = secp256k1.S256 23 | var SigToPub = crypto.SigToPub 24 | -------------------------------------------------------------------------------- /pkg/ecdsa/tss/tss_ethereum_test.go: -------------------------------------------------------------------------------- 1 | //+build !celo 2 | 3 | package tss 4 | 5 | import ( 6 | "github.com/ethereum/go-ethereum/crypto" 7 | "github.com/ethereum/go-ethereum/crypto/secp256k1" 8 | ) 9 | 10 | // Components defined in this file act as a facade in front of components 11 | // provided by the `go-ethereum/crypto` package. This way, the client code 12 | // does not have to import `go-ethereum/crypto` directly. As result, the 13 | // backing package can be swapped to an other `go-ethereum/crypto`-like package 14 | // at build time, via according build tags. That solution aims to address 15 | // problems with package conflicts as package `go-ethereum/crypto` uses some 16 | // native C code underneath. If another `go-ethereum/crypto`-like package 17 | // is used in the same time, the compilation may fail due to C linker errors 18 | // caused by duplicated symbols. Such a problem can be observed while trying 19 | // to use the `go-ethereum/crypto` and `celo-blockchain/crypto` at the 20 | // same time. 21 | 22 | var S256 = secp256k1.S256 23 | var SigToPub = crypto.SigToPub 24 | -------------------------------------------------------------------------------- /pkg/extensions/tbtc/config.go: -------------------------------------------------------------------------------- 1 | package tbtc 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/keep-network/keep-ecdsa/pkg/chain/bitcoin" 7 | 8 | configtime "github.com/keep-network/keep-ecdsa/config/time" 9 | ) 10 | 11 | const ( 12 | // The default value of a timeout for liquidation recovery. 13 | defaultLiquidationRecoveryTimeout = 48 * time.Hour 14 | ) 15 | 16 | // Config stores configuration of application extensions responsible for 17 | // executing signer actions specific for TBTC application. 18 | type Config struct { 19 | TBTCSystem string 20 | Bitcoin bitcoin.Config 21 | LiquidationRecoveryTimeout configtime.Duration 22 | } 23 | 24 | // GetLiquidationRecoveryTimeout returns the liquidation recovery timeout. If a 25 | // value is not set it returns a default value. 26 | func (c *Config) GetLiquidationRecoveryTimeout() time.Duration { 27 | timeout := c.LiquidationRecoveryTimeout.ToDuration() 28 | if timeout == 0 { 29 | timeout = defaultLiquidationRecoveryTimeout 30 | } 31 | 32 | return timeout 33 | } 34 | -------------------------------------------------------------------------------- /pkg/extensions/tbtc/recovery/resolve_address.go: -------------------------------------------------------------------------------- 1 | package recovery 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | "github.com/btcsuite/btcutil" 6 | "github.com/keep-network/keep-ecdsa/pkg/chain/bitcoin" 7 | ) 8 | 9 | // ResolveAddress resolves a configured beneficiaryAddress into a 10 | // valid bitcoin address. If the supplied address is already a valid bitcoin 11 | // address, we don't have to do anything. If the supplied address is an 12 | // extended public key of a HD wallet, attempt to derive the bitcoin address at 13 | // the specified index. The function will store an index of the last resolved 14 | // bitcoin address unless it is a dry run. 15 | // 16 | // The function does not validate inputs. It is expected that validations are 17 | // performed before calling this function. Especially the beneficiary address 18 | // should be validated with the given chain network type. 19 | func ResolveAddress( 20 | beneficiaryAddress string, 21 | storage *DerivationIndexStorage, 22 | chainParams *chaincfg.Params, 23 | handle bitcoin.Handle, 24 | isDryRun bool, 25 | ) (string, error) { 26 | // If the address decodes without error, then we have a valid bitcoin 27 | // address. Otherwise, we assume that it's an extended key and we attempt to 28 | // derive the address. 29 | decodedAddress, err := btcutil.DecodeAddress(beneficiaryAddress, chainParams) 30 | if err != nil { 31 | derivedAddress, err := storage.GetNextAddress( 32 | beneficiaryAddress, 33 | handle, 34 | chainParams, 35 | isDryRun, 36 | ) 37 | if err != nil { 38 | return "", err 39 | } 40 | return derivedAddress, nil 41 | } 42 | return decodedAddress.EncodeAddress(), nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/ipfs/go-log" 8 | "github.com/keep-network/keep-ecdsa/pkg/client" 9 | 10 | "github.com/keep-network/keep-common/pkg/metrics" 11 | ) 12 | 13 | var logger = log.Logger("keep-metrics") 14 | 15 | const ( 16 | // DefaultClientMetricsTick is the default duration of the 17 | // observation tick for client metrics. 18 | DefaultClientMetricsTick = 1 * time.Minute 19 | ) 20 | 21 | // ObserveTSSPreParamsPoolSize triggers an observation process of the 22 | // tss_pre_params_pool_size metric. 23 | func ObserveTSSPreParamsPoolSize( 24 | ctx context.Context, 25 | registry *metrics.Registry, 26 | clientHandle *client.Handle, 27 | tick time.Duration, 28 | ) { 29 | input := func() float64 { 30 | return float64(clientHandle.TSSPreParamsPoolSize()) 31 | } 32 | 33 | observe( 34 | ctx, 35 | "tss_pre_params_pool_size", 36 | input, 37 | registry, 38 | validateTick(tick, DefaultClientMetricsTick), 39 | ) 40 | } 41 | 42 | func observe( 43 | ctx context.Context, 44 | name string, 45 | input metrics.ObserverInput, 46 | registry *metrics.Registry, 47 | tick time.Duration, 48 | ) { 49 | observer, err := registry.NewGaugeObserver(name, input) 50 | if err != nil { 51 | logger.Warningf("could not create gauge observer [%v]", name) 52 | return 53 | } 54 | 55 | observer.Observe(ctx, tick) 56 | } 57 | 58 | func validateTick(tick time.Duration, defaultTick time.Duration) time.Duration { 59 | if tick > 0 { 60 | return tick 61 | } 62 | 63 | return defaultTick 64 | } 65 | -------------------------------------------------------------------------------- /pkg/node/params_pool.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/binance-chain/tss-lib/ecdsa/keygen" 7 | "github.com/keep-network/keep-ecdsa/pkg/ecdsa/tss" 8 | ) 9 | 10 | // tssPreParamsPool is a pool holding TSS pre parameters. It autogenerates entries 11 | // up to the pool size. When an entry is pulled from the pool it will generate 12 | // new entry. 13 | type tssPreParamsPool struct { 14 | pool chan *keygen.LocalPreParams 15 | new func() (*keygen.LocalPreParams, error) 16 | } 17 | 18 | // InitializeTSSPreParamsPool generates TSS pre-parameters and stores them in a pool. 19 | func (n *Node) InitializeTSSPreParamsPool() { 20 | poolSize := n.tssConfig.GetPreParamsTargetPoolSize() 21 | 22 | logger.Infof("TSS pre-parameters target pool size is [%v]", poolSize) 23 | 24 | n.tssParamsPool = &tssPreParamsPool{ 25 | pool: make(chan *keygen.LocalPreParams, poolSize), 26 | new: func() (*keygen.LocalPreParams, error) { 27 | return tss.GenerateTSSPreParams( 28 | n.tssConfig.GetPreParamsGenerationTimeout(), 29 | ) 30 | }, 31 | } 32 | 33 | go n.tssParamsPool.pumpPool() 34 | } 35 | 36 | // TSSPreParamsPoolSize returns the current size of the TSS params pool. 37 | func (n *Node) TSSPreParamsPoolSize() int { 38 | if n.tssParamsPool == nil { 39 | return 0 40 | } 41 | 42 | return len(n.tssParamsPool.pool) 43 | } 44 | 45 | func (t *tssPreParamsPool) pumpPool() { 46 | for { 47 | logger.Info("generating new tss pre parameters") 48 | 49 | start := time.Now() 50 | 51 | params, err := t.new() 52 | if err != nil { 53 | logger.Warningf( 54 | "failed to generate tss pre parameters after [%s]: [%v]", 55 | time.Since(start), 56 | err, 57 | ) 58 | continue 59 | } 60 | 61 | logger.Infof( 62 | "generated new tss pre parameters, took: [%s], current pool size: [%d]", 63 | time.Since(start), 64 | len(t.pool)+1, 65 | ) 66 | 67 | t.pool <- params 68 | } 69 | } 70 | 71 | // get returns TSS pre parameters from the pool. It pumps the pool after getting 72 | // and entry. If the pool is empty it will wait for a new entry to be generated. 73 | func (t *tssPreParamsPool) get() *keygen.LocalPreParams { 74 | return <-t.pool 75 | } 76 | -------------------------------------------------------------------------------- /pkg/utils/btc_transaction.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/btcsuite/btcd/wire" 8 | ) 9 | 10 | // DeserializeTransaction decodes a transaction from bitcoin hexadecimal format 11 | // to a bitcoin message. 12 | func DeserializeTransaction(transactionHex []byte) (*wire.MsgTx, error) { 13 | msgTx := wire.NewMsgTx(wire.TxVersion) 14 | 15 | reader := bytes.NewReader(transactionHex) 16 | err := msgTx.Deserialize(reader) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to deserialize transaction: [%v]", err) 19 | } 20 | 21 | return msgTx, nil 22 | } 23 | 24 | // SerializeTransaction encodes a bitcoin transaction message to a hexadecimal 25 | // format. 26 | func SerializeTransaction(msgTx *wire.MsgTx) ([]byte, error) { 27 | var buffer bytes.Buffer 28 | 29 | err := msgTx.Serialize(&buffer) 30 | if err != nil { 31 | return nil, fmt.Errorf("failed to serialize transaction: [%v]", err) 32 | } 33 | 34 | return buffer.Bytes(), nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/utils/btc_transaction_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "testing" 7 | ) 8 | 9 | func TestSerializeTransactionRoundTrip(t *testing.T) { 10 | expectedTransaction, _ := hex.DecodeString("0100000000010199dffb02e8f3e3f8053eecf6110a95f77ba658690dfc3a447b7e52cf34ca135e0000000000ffffffff02581b000000000000160014d849b1e1cede2ac7d7188cf8700e97d6975c91c4e8030000000000001976a914d849b1e1cede2ac7d7188cf8700e97d6975c91c488ac02483045022100ecadce07f5c9d84b4fa1b2728806135acd81ad9398c9673eeb4e161d42364b92022076849daa2108ed2a135d16eb9e103c5819db014ea2bad5c92f4aeecf47bf9ac80121028896955d043b5a43957b21901f2cce9f0bfb484531b03ad6cd3153e45e73ee2e00000000") 11 | 12 | msgTx, err := DeserializeTransaction(expectedTransaction) 13 | if err != nil { 14 | t.Fatalf("transaction deserialization failed: [%v]", err) 15 | } 16 | 17 | result, err := SerializeTransaction(msgTx) 18 | if err != nil { 19 | t.Fatalf("transaction serialization failed: [%v]", err) 20 | } 21 | 22 | if !bytes.Equal(result, expectedTransaction) { 23 | t.Errorf("unexpected transaction\nexpected: %x\nactual: %x\n", 24 | expectedTransaction, 25 | result, 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/utils/byteutils/byteutils.go: -------------------------------------------------------------------------------- 1 | // Package byteutils provides helper utilities for working with bytes 2 | package byteutils 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | // LeftPadTo32Bytes appends zeros to bytes slice to make it exactly 32 bytes long. 9 | // TODO: This is copied from keep-core. Consider extracting utils to a separate repo. 10 | func LeftPadTo32Bytes(bytes []byte) ([]byte, error) { 11 | expectedByteLen := 32 12 | if len(bytes) > expectedByteLen { 13 | return nil, fmt.Errorf( 14 | "cannot pad %v byte array to %v bytes", len(bytes), expectedByteLen, 15 | ) 16 | } 17 | 18 | result := make([]byte, 0) 19 | if len(bytes) < expectedByteLen { 20 | result = make([]byte, expectedByteLen-len(bytes)) 21 | } 22 | result = append(result, bytes...) 23 | 24 | return result, nil 25 | } 26 | 27 | // BytesTo32Byte converts bytes slice to a 32-byte array. It left pads the array 28 | // with zeros in case of a slice shorter than 32-byte. 29 | func BytesTo32Byte(bytes []byte) ([32]byte, error) { 30 | var result [32]byte 31 | 32 | paddedBytes, err := LeftPadTo32Bytes(bytes) 33 | if err != nil { 34 | return result, err 35 | } 36 | 37 | copy(result[:], paddedBytes[:32]) 38 | 39 | return result, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/utils/pbutils/pbutils.go: -------------------------------------------------------------------------------- 1 | package pbutils 2 | 3 | import ( 4 | "github.com/gogo/protobuf/proto" 5 | fuzz "github.com/google/gofuzz" 6 | ) 7 | 8 | // RoundTrip is code borrowed from keep-core/pkg/internal/pbutils. 9 | // TODO: Extract this code from `keep-core` to `keep-common`. 10 | func RoundTrip( 11 | marshaler proto.Marshaler, 12 | unmarshaler proto.Unmarshaler, 13 | ) error { 14 | bytes, err := marshaler.Marshal() 15 | if err != nil { 16 | return err 17 | } 18 | 19 | err = unmarshaler.Unmarshal(bytes) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | return nil 25 | } 26 | 27 | // FuzzUnmarshaler tests given unmarshaler with random bytes. 28 | func FuzzUnmarshaler(unmarshaler proto.Unmarshaler) { 29 | for i := 0; i < 100; i++ { 30 | var messageBytes []byte 31 | 32 | f := fuzz.New().NilChance(0.01).NumElements(0, 512) 33 | f.Fuzz(&messageBytes) 34 | 35 | _ = unmarshaler.Unmarshal(messageBytes) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripts/macos-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Installing golang requirements..." 5 | brew list golang &>/dev/null || brew install golang 6 | 7 | echo "Installing jq..." 8 | brew list jq &>/dev/null || brew install jq 9 | 10 | echo "Installing ethereum requirements..." 11 | brew tap ethereum/ethereum 12 | brew list geth &>/dev/null || brew install geth 13 | brew list solidity &>/dev/null || brew install solidity 14 | 15 | echo "Installing precommit requirements..." 16 | brew list pre-commit &>/dev/null || brew install pre-commit 17 | go get -u golang.org/x/tools/cmd/goimports 18 | go get -u golang.org/x/lint/golint 19 | 20 | echo "Installing pre-commit and specified hooks..." 21 | pre-commit install --install-hooks 22 | 23 | echo "Installing solidity npm and requirements..." 24 | brew list npm &>/dev/null || brew install npm 25 | cd ../contracts/solidity && npm install && cd ../../scripts 26 | 27 | echo "Installing truffle..." 28 | npm install -g truffle 29 | 30 | if ! [ -x "$(command -v protoc-gen-gogoslick)" ]; then 31 | echo 'WARNING: protoc-gen-gogoslick command is not available' 32 | echo 'WARNING: please check whether $GOPATH/bin is added to your $PATH' 33 | fi 34 | 35 | echo "Ready to rock! See above for any extra environment-related instructions." 36 | -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Dafault inputs. 5 | KEEP_ETHEREUM_PASSWORD_DEFAULT=password 6 | LOG_LEVEL_DEFAULT="info" 7 | CONFIG_DIR_PATH_DEFAULT=$(realpath -m $(dirname $0)/../configs) 8 | 9 | # FIXME: set this value as env var when calling this script. Approach should 10 | # stay consistent between 'install.sh' & 'start.sh' scripts. 11 | # 12 | # Read user inputs. 13 | read -p "Enter ethereum accounts password [$KEEP_ETHEREUM_PASSWORD_DEFAULT]: " ethereum_password 14 | KEEP_ETHEREUM_PASSWORD=${ethereum_password:-$KEEP_ETHEREUM_PASSWORD_DEFAULT} 15 | 16 | # Transform long options to short ones 17 | for arg in "$@"; do 18 | shift 19 | case "$arg" in 20 | "--config-dir") set -- "$@" "-c" ;; 21 | *) set -- "$@" "$arg" ;; 22 | esac 23 | done 24 | 25 | # Parse short options 26 | OPTIND=1 27 | while getopts "c:" opt; do 28 | case "$opt" in 29 | c) config_dir_path="$OPTARG" ;; 30 | esac 31 | done 32 | shift $(expr $OPTIND - 1) # remove options from positional parameters 33 | 34 | CONFIG_DIR_PATH=${config_dir_path:-$CONFIG_DIR_PATH_DEFAULT} 35 | 36 | config_files=($CONFIG_DIR_PATH/*.toml) 37 | config_files_count=${#config_files[@]} 38 | while :; do 39 | printf "\nSelect client config file: \n" 40 | i=1 41 | for o in "${config_files[@]}"; do 42 | echo "$i) ${o##*/}" 43 | let i++ 44 | done 45 | 46 | read reply 47 | if [ "$reply" -ge 1 ] && [ "$reply" -le $config_files_count ]; then 48 | CONFIG_FILE_PATH=${config_files["$reply" - 1]} 49 | break 50 | else 51 | printf "\nInvalid choice. Please choose an existing option number.\n" 52 | fi 53 | done 54 | printf "\nClient config file: \"$CONFIG_FILE_PATH\" \n\n" 55 | 56 | log_level_options=("info" "debug" "custom...") 57 | while :; do 58 | echo "Select log level [$LOG_LEVEL_DEFAULT]: " 59 | i=1 60 | for o in "${log_level_options[@]}"; do 61 | echo "$i) $o" 62 | let i++ 63 | done 64 | 65 | read reply 66 | case $reply in 67 | "1" | "${log_level_options[0]}") 68 | LOG_LEVEL=${log_level_options[0]} 69 | break 70 | ;; 71 | "2" | "${log_level_options[1]}") 72 | LOG_LEVEL=${log_level_options[1]} 73 | break 74 | ;; 75 | "3" | "${log_level_options[2]}") 76 | read -p "Enter custom log level: [$LOG_LEVEL_DEFAULT]" log_level 77 | LOG_LEVEL=${log_level:-$LOG_LEVEL_DEFAULT} 78 | break 79 | ;; 80 | "") 81 | LOG_LEVEL=$LOG_LEVEL_DEFAULT 82 | break 83 | ;; 84 | *) echo "Invalid choice. Please choose an existing option number." ;; 85 | esac 86 | done 87 | echo "Log level: \"$LOG_LEVEL\"" 88 | 89 | # Run script. 90 | LOG_START='\n\e[1;36m' # new line + bold + color 91 | LOG_END='\n\e[0m' # new line + reset color 92 | 93 | KEEP_ECDSA_PATH=$(realpath $(dirname $0)/../) 94 | KEEP_ECDSA_CONFIG_FILE_PATH=$(realpath $CONFIG_FILE_PATH) 95 | 96 | printf "${LOG_START}Starting keep-ecdsa client...${LOG_END}" 97 | cd $KEEP_ECDSA_PATH 98 | KEEP_ETHEREUM_PASSWORD=$KEEP_ETHEREUM_PASSWORD \ 99 | LOG_LEVEL=${LOG_LEVEL} \ 100 | ./keep-ecdsa --config $KEEP_ECDSA_CONFIG_FILE_PATH start 101 | -------------------------------------------------------------------------------- /solidity/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /solidity/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-keep"], 3 | "parserOptions": { 4 | "ecmaVersion": 2017, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "es6": true, 9 | "mocha": true 10 | }, 11 | "rules": { 12 | "new-cap": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solidity/.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "keep", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "security/no-block-members": [ 8 | "off", 9 | [ 10 | "timestamp" 11 | ] 12 | ], 13 | "indentation": "off", 14 | "security/no-call-value": "off", 15 | "security/no-inline-assembly": "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /solidity/contracts/ECDSARewardsDistributorEscrow.sol: -------------------------------------------------------------------------------- 1 | /** 2 | ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄ 3 | ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 4 | ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ 5 | ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ 6 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓▌ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 7 | ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀ 8 | ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ 9 | ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ 10 | ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ 11 | 12 | Trust math, not hardware. 13 | */ 14 | 15 | pragma solidity 0.5.17; 16 | 17 | import "@keep-network/keep-core/contracts/PhasedEscrow.sol"; 18 | import "./ECDSARewardsDistributor.sol"; 19 | 20 | /// @title ECDSARewardsDistributorEscrow 21 | /// @notice A token holder contract allowing contract owner to allocate rewards 22 | /// to ECDSARewardsDistributor in phases. Each phase corresponds to 23 | /// a new reward interval allocated with its own merkle root used by 24 | /// stakers to withdraw their rewards. 25 | contract ECDSARewardsDistributorEscrow is PhasedEscrow { 26 | ECDSARewardsDistributor public ecdsaRewardsDistributor; 27 | 28 | constructor(IERC20 _token, ECDSARewardsDistributor _ecdsaRewardsDistributor) 29 | public 30 | PhasedEscrow(_token) 31 | { 32 | ecdsaRewardsDistributor = _ecdsaRewardsDistributor; 33 | } 34 | 35 | function allocateInterval(bytes32 merkleRoot, uint256 amount) 36 | external 37 | onlyOwner 38 | { 39 | token.approve(address(ecdsaRewardsDistributor), amount); 40 | ecdsaRewardsDistributor.allocate(merkleRoot, amount); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /solidity/contracts/ECDSARewardsEscrowBeneficiary.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@keep-network/keep-core/contracts/PhasedEscrow.sol"; 4 | 5 | /// @title ECDSARewardsEscrowBeneficiary 6 | /// @notice Transfer the received tokens from PhasedEscrow to a designated 7 | /// ECDSARewards contract. 8 | contract ECDSARewardsEscrowBeneficiary is StakerRewardsBeneficiary { 9 | constructor(IERC20 _token, IStakerRewards _stakerRewards) 10 | public 11 | StakerRewardsBeneficiary(_token, _stakerRewards) 12 | {} 13 | } 14 | 15 | /// @title ECDSABackportRewardsEscrowBeneficiary 16 | /// @notice Trasfer the received tokens from Phased Escrow to a designated 17 | /// ECDSABackportRewards contract. 18 | contract ECDSABackportRewardsEscrowBeneficiary is StakerRewardsBeneficiary { 19 | constructor(IERC20 _token, IStakerRewards _stakerRewards) 20 | public 21 | StakerRewardsBeneficiary(_token, _stakerRewards) 22 | {} 23 | } 24 | -------------------------------------------------------------------------------- /solidity/contracts/KeepCreator.sol: -------------------------------------------------------------------------------- 1 | /** 2 | ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄ 3 | ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 4 | ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ 5 | ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ 6 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 7 | ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀ 8 | ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ 9 | ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ 10 | ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ 11 | 12 | Trust math, not hardware. 13 | */ 14 | 15 | pragma solidity 0.5.17; 16 | 17 | import "./CloneFactory.sol"; 18 | 19 | contract KeepCreator is CloneFactory { 20 | // Holds the address of the keep contract that will be used as a master 21 | // contract for cloning. 22 | address public masterKeepAddress; 23 | 24 | // Keeps created by this factory. 25 | address[] public keeps; 26 | 27 | // Maps keep opened timestamp to each keep address 28 | mapping(address => uint256) keepOpenedTimestamp; 29 | 30 | constructor(address _masterKeepAddress) public { 31 | masterKeepAddress = _masterKeepAddress; 32 | } 33 | 34 | /// @notice Gets how many keeps have been opened by this contract. 35 | /// @dev Checks the size of the keeps array. 36 | /// @return The number of keeps opened so far. 37 | function getKeepCount() external view returns (uint256) { 38 | return keeps.length; 39 | } 40 | 41 | /// @notice Gets a specific keep address at a given index. 42 | /// @return The address of the keep at the given index. 43 | function getKeepAtIndex(uint256 index) external view returns (address) { 44 | require(index < keeps.length, "Out of bounds."); 45 | return keeps[index]; 46 | } 47 | 48 | /// @notice Gets the opened timestamp of the given keep. 49 | /// @return Timestamp the given keep was opened at or 0 if this keep 50 | /// was not created by this factory. 51 | function getKeepOpenedTimestamp(address _keep) 52 | external 53 | view 54 | returns (uint256) 55 | { 56 | return keepOpenedTimestamp[_keep]; 57 | } 58 | 59 | /// @notice Creates a new keep instance with a clone factory. 60 | /// @dev It has to be called by a function implementing a keep opening mechanism. 61 | function createKeep() internal returns (address keepAddress) { 62 | keepAddress = createClone(masterKeepAddress); 63 | keeps.push(keepAddress); 64 | 65 | /* solium-disable-next-line security/no-block-members*/ 66 | keepOpenedTimestamp[keepAddress] = block.timestamp; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /solidity/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint256 completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solidity/contracts/api/IBondedECDSAKeepVendor.sol: -------------------------------------------------------------------------------- 1 | /** 2 | ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄ 3 | ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 4 | ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ 5 | ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ 6 | ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 7 | ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀ 8 | ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ 9 | ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ 10 | ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ 11 | 12 | Trust math, not hardware. 13 | */ 14 | 15 | pragma solidity 0.5.17; 16 | 17 | /// @title ECDSA Keep Vendor 18 | /// @notice Contract reflecting an ECDSA keep vendor. 19 | contract IBondedECDSAKeepVendor { 20 | /// @notice Select a recommended ECDSA keep factory from all registered 21 | /// ECDSA keep factories. 22 | /// @return Selected ECDSA keep factory address. 23 | function selectFactory() public view returns (address payable); 24 | } 25 | -------------------------------------------------------------------------------- /solidity/contracts/test/AbstractBondingStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/AbstractBonding.sol"; 4 | 5 | import "./StakingInfoStub.sol"; 6 | 7 | contract AbstractBondingStub is AbstractBonding { 8 | StakingInfoStub stakingInfoStub; 9 | 10 | constructor(address registryAddress, address stakingInfoAddress) 11 | public 12 | AbstractBonding(registryAddress) 13 | { 14 | stakingInfoStub = StakingInfoStub(stakingInfoAddress); 15 | } 16 | 17 | function withdraw(uint256 amount, address operator) public { 18 | revert("abstract function"); 19 | } 20 | 21 | function withdrawBondExposed(uint256 amount, address operator) public { 22 | withdrawBond(amount, operator); 23 | } 24 | 25 | function isAuthorizedForOperator( 26 | address _operator, 27 | address _operatorContract 28 | ) public view returns (bool) { 29 | return 30 | stakingInfoStub.isAuthorizedForOperator( 31 | _operator, 32 | _operatorContract 33 | ); 34 | } 35 | 36 | function authorizerOf(address _operator) public view returns (address) { 37 | return stakingInfoStub.authorizerOf(_operator); 38 | } 39 | 40 | function beneficiaryOf(address _operator) 41 | public 42 | view 43 | returns (address payable) 44 | { 45 | return stakingInfoStub.beneficiaryOf(_operator); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /solidity/contracts/test/BondedECDSAKeepCloneFactoryStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/BondedECDSAKeep.sol"; 4 | import "../../contracts/CloneFactory.sol"; 5 | 6 | /// @title Bonded ECDSA Keep Factory Stub using clone factory. 7 | /// @dev This contract is for testing purposes only. 8 | contract BondedECDSAKeepCloneFactory is CloneFactory { 9 | uint256 public minimumStake = 200000 * 1e18; 10 | 11 | address public masterBondedECDSAKeepAddress; 12 | bool public membersSlashed; 13 | 14 | constructor(address _masterBondedECDSAKeepAddress) public { 15 | masterBondedECDSAKeepAddress = _masterBondedECDSAKeepAddress; 16 | } 17 | 18 | event BondedECDSAKeepCreated(address keepAddress); 19 | 20 | function newKeep( 21 | address _owner, 22 | address[] calldata _members, 23 | uint256 _honestThreshold, 24 | uint256 _minimumStake, 25 | uint256 _stakeLockDuration, 26 | address _tokenStaking, 27 | address _keepBonding, 28 | address payable _keepFactory 29 | ) external payable returns (address keepAddress) { 30 | keepAddress = createClone(masterBondedECDSAKeepAddress); 31 | assert(isClone(masterBondedECDSAKeepAddress, keepAddress)); 32 | 33 | BondedECDSAKeep keep = BondedECDSAKeep(keepAddress); 34 | keep.initialize( 35 | _owner, 36 | _members, 37 | _honestThreshold, 38 | _minimumStake, 39 | _stakeLockDuration, 40 | _tokenStaking, 41 | _keepBonding, 42 | _keepFactory 43 | ); 44 | 45 | emit BondedECDSAKeepCreated(keepAddress); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /solidity/contracts/test/BondedECDSAKeepFactoryStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/BondedECDSAKeepFactory.sol"; 4 | 5 | /// @title Bonded ECDSA Keep Factory Stub 6 | /// @dev This contract is for testing purposes only. 7 | contract BondedECDSAKeepFactoryStub is BondedECDSAKeepFactory { 8 | constructor( 9 | address masterBondedECDSAKeepAddress, 10 | address sortitionPoolFactory, 11 | address tokenStaking, 12 | address keepBonding, 13 | address randomBeacon 14 | ) 15 | public 16 | BondedECDSAKeepFactory( 17 | masterBondedECDSAKeepAddress, 18 | sortitionPoolFactory, 19 | tokenStaking, 20 | keepBonding, 21 | randomBeacon 22 | ) 23 | {} 24 | 25 | function initialGroupSelectionSeed(uint256 _groupSelectionSeed) public { 26 | groupSelectionSeed = _groupSelectionSeed; 27 | } 28 | 29 | function getGroupSelectionSeed() public view returns (uint256) { 30 | return groupSelectionSeed; 31 | } 32 | 33 | /// @notice Opens a new ECDSA keep. 34 | /// @param _owner Address of the keep owner. 35 | /// @param _members Keep members. 36 | /// @param _creationTimestamp Keep creation timestamp. 37 | /// 38 | /// @return Created keep address. 39 | function stubOpenKeep( 40 | address _owner, 41 | address[] memory _members, 42 | uint256 _creationTimestamp 43 | ) public returns (address keepAddress) { 44 | keepAddress = createClone(masterKeepAddress); 45 | BondedECDSAKeep keep = BondedECDSAKeep(keepAddress); 46 | keep.initialize( 47 | _owner, 48 | _members, 49 | 0, 50 | 0, 51 | 0, 52 | address(tokenStaking), 53 | address(keepBonding), 54 | address(this) 55 | ); 56 | keeps.push(keepAddress); 57 | keepOpenedTimestamp[keepAddress] = _creationTimestamp; 58 | } 59 | 60 | /// @notice Mocks opening `_numberOfKeeps` with the opening timestamp of 61 | /// the first keep starting at `_firstKeepCreationTimestamp`. 62 | /// IMPORTANT! This function does not actually create new keep instances! 63 | /// It would not be possible to create 100 keeps in one block because of 64 | /// gas limits. 65 | function stubBatchOpenFakeKeeps( 66 | uint256 _numberOfKeeps, 67 | uint256 _firstKeepCreationTimestamp 68 | ) public { 69 | for (uint256 i = 0; i < _numberOfKeeps; i++) { 70 | address keepAddress = address(block.timestamp.add(i)); 71 | keeps.push(keepAddress); 72 | keepOpenedTimestamp[keepAddress] = _firstKeepCreationTimestamp.add( 73 | i 74 | ); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /solidity/contracts/test/BondedECDSAKeepFactoryVendorStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/BondedECDSAKeepFactory.sol"; 4 | 5 | /// @title Bonded ECDSA Keep Factory Stub for vendor testing 6 | /// @dev This contract is for testing purposes only. 7 | contract BondedECDSAKeepFactoryVendorStub is BondedECDSAKeepFactory { 8 | constructor( 9 | address masterBondedECDSAKeepAddress, 10 | address sortitionPoolFactory, 11 | address tokenStaking, 12 | address keepBonding, 13 | address randomBeacon 14 | ) 15 | public 16 | BondedECDSAKeepFactory( 17 | masterBondedECDSAKeepAddress, 18 | sortitionPoolFactory, 19 | tokenStaking, 20 | keepBonding, 21 | randomBeacon 22 | ) 23 | {} 24 | 25 | // @dev Returns calculated keep address. 26 | function openKeep( 27 | uint256 _groupSize, 28 | uint256 _honestThreshold, 29 | address _owner, 30 | uint256 _bond 31 | ) public payable returns (address) { 32 | _groupSize; 33 | _honestThreshold; 34 | _owner; 35 | _bond; 36 | 37 | return calculateKeepAddress(); 38 | } 39 | 40 | /// @dev Calculates an address for a keep based on the address of the factory. 41 | /// We need it to have predictable addresses for factories verification. 42 | function calculateKeepAddress() public view returns (address) { 43 | uint256 factoryAddressInt = uint256(address(this)); 44 | return address(factoryAddressInt % 1000000000000); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /solidity/contracts/test/BondedECDSAKeepStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/BondedECDSAKeep.sol"; 4 | 5 | /// @title Bonded ECDSA Keep Stub 6 | /// @dev This contract is for testing purposes only. 7 | contract BondedECDSAKeepStub is BondedECDSAKeep { 8 | function publicMarkAsClosed() public { 9 | markAsClosed(); 10 | } 11 | 12 | function publicMarkAsTerminated() public { 13 | markAsTerminated(); 14 | } 15 | 16 | function isFradulentPreimageSet(bytes memory preimage) 17 | public 18 | view 19 | returns (bool) 20 | { 21 | return fraudulentPreimages[preimage]; 22 | } 23 | 24 | function setMemberStake(uint256 stake) public { 25 | memberStake = stake; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solidity/contracts/test/BondedECDSAKeepVendorImplV1Stub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/BondedECDSAKeepVendorImplV1.sol"; 4 | 5 | /// @title Bonded ECDSA Keep Vendor Implementation Stub 6 | /// @dev This contract is for testing purposes only. 7 | contract BondedECDSAKeepVendorImplV1Stub is BondedECDSAKeepVendorImplV1 { 8 | function getVersion() public view returns (string memory) { 9 | return "V1"; 10 | } 11 | 12 | function getKeepFactory() public view returns (address) { 13 | return keepFactory; 14 | } 15 | 16 | function getNewKeepFactory() public view returns (address) { 17 | return newKeepFactory; 18 | } 19 | 20 | function getFactoryRegistrationInitiatedTimestamp() 21 | public 22 | view 23 | returns (uint256) 24 | { 25 | return factoryUpgradeInitiatedTimestamp; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solidity/contracts/test/BondedECDSAKeepVendorImplV2Stub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /// @title Bonded ECDSA Keep Vendor Implementation V2 Stub 4 | /// @dev This contract is for testing purposes only. 5 | contract BondedECDSAKeepVendorImplV2Stub { 6 | mapping(string => bool) internal _initialized; 7 | 8 | function version() public view returns (string memory) { 9 | return "V2"; 10 | } 11 | 12 | function initialize(bool shouldFail) public { 13 | require(!initialized(), "Contract is already initialized."); 14 | _initialized["BondedECDSAKeepVendorImplV2"] = true; 15 | 16 | require(!shouldFail, "Initialization failed"); 17 | } 18 | 19 | function initialized() public view returns (bool) { 20 | return _initialized["BondedECDSAKeepVendorImplV2"]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solidity/contracts/test/ECDSARewardsStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/ECDSARewards.sol"; 4 | 5 | /// @title ECDSA Rewards Stub for ecdsa rewards testing 6 | /// @dev This contract is for testing purposes only. 7 | contract ECDSARewardsStub is ECDSARewards { 8 | constructor( 9 | address _token, 10 | address payable _factoryAddress, 11 | address _tokenStakingAddress 12 | ) public ECDSARewards(_token, _factoryAddress, _tokenStakingAddress) {} 13 | 14 | function setBeneficiaryRewardCap(uint256 _beneficiaryRewardCap) public { 15 | beneficiaryRewardCap = _beneficiaryRewardCap; 16 | } 17 | 18 | function allocateReward( 19 | address operator, 20 | uint256 interval, 21 | uint256 amount 22 | ) public { 23 | address beneficiary = tokenStaking.beneficiaryOf(operator); 24 | allocatedRewards[beneficiary][interval] = amount; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solidity/contracts/test/FullyBackedBondingStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../fully-backed/FullyBackedBonding.sol"; 4 | 5 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 6 | 7 | /// @title Fully Backed Bonding Stub 8 | /// @dev This contract is for testing purposes only. 9 | contract FullyBackedBondingStub is FullyBackedBonding { 10 | // using SafeMath for uint256; 11 | // // address public delegatedAuthority; 12 | // bool slashingShouldFail; 13 | constructor(KeepRegistry _keepRegistry, uint256 _initializationPeriod) 14 | public 15 | FullyBackedBonding(_keepRegistry, _initializationPeriod) 16 | {} 17 | 18 | function setBeneficiary(address _operator, address payable _beneficiary) 19 | public 20 | { 21 | operators[_operator].beneficiary = _beneficiary; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solidity/contracts/test/FullyBackedECDSAKeepCloneFactoryStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/fully-backed/FullyBackedECDSAKeep.sol"; 4 | import "../../contracts/CloneFactory.sol"; 5 | 6 | import { 7 | AuthorityDelegator 8 | } from "@keep-network/keep-core/contracts/Authorizations.sol"; 9 | 10 | /// @title Fully Backed Bonded ECDSA Keep Factory Stub using clone factory. 11 | /// @dev This contract is for testing purposes only. 12 | contract FullyBackedECDSAKeepCloneFactoryStub is 13 | CloneFactory, 14 | AuthorityDelegator 15 | { 16 | address public masterKeepAddress; 17 | 18 | mapping(address => uint256) public banKeepMembersCalledCount; 19 | 20 | constructor(address _masterKeepAddress) public { 21 | masterKeepAddress = _masterKeepAddress; 22 | } 23 | 24 | event FullyBackedECDSAKeepCreated(address keepAddress); 25 | 26 | function newKeep( 27 | address _owner, 28 | address[] calldata _members, 29 | uint256 _honestThreshold, 30 | address _keepBonding, 31 | address payable _keepFactory 32 | ) external payable returns (address keepAddress) { 33 | keepAddress = createClone(masterKeepAddress); 34 | assert(isClone(masterKeepAddress, keepAddress)); 35 | 36 | FullyBackedECDSAKeep keep = FullyBackedECDSAKeep(keepAddress); 37 | keep.initialize( 38 | _owner, 39 | _members, 40 | _honestThreshold, 41 | _keepBonding, 42 | _keepFactory 43 | ); 44 | 45 | emit FullyBackedECDSAKeepCreated(keepAddress); 46 | } 47 | 48 | function __isRecognized(address _delegatedAuthorityRecipient) 49 | external 50 | returns (bool) 51 | { 52 | return true; 53 | } 54 | 55 | function banKeepMembers() public { 56 | banKeepMembersCalledCount[msg.sender]++; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /solidity/contracts/test/FullyBackedECDSAKeepFactoryStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/fully-backed/FullyBackedECDSAKeepFactory.sol"; 4 | 5 | /// @title Fully Backed Bonded ECDSA Keep Factory Stub 6 | /// @dev This contract is for testing purposes only. 7 | contract FullyBackedECDSAKeepFactoryStub is FullyBackedECDSAKeepFactory { 8 | constructor( 9 | address _masterKeepAddress, 10 | address _sortitionPoolFactoryAddress, 11 | address _bondingAddress, 12 | address _randomBeaconAddress 13 | ) 14 | public 15 | FullyBackedECDSAKeepFactory( 16 | _masterKeepAddress, 17 | _sortitionPoolFactoryAddress, 18 | _bondingAddress, 19 | _randomBeaconAddress 20 | ) 21 | {} 22 | 23 | function initialGroupSelectionSeed(uint256 _groupSelectionSeed) public { 24 | groupSelectionSeed = _groupSelectionSeed; 25 | } 26 | 27 | function getGroupSelectionSeed() public view returns (uint256) { 28 | return groupSelectionSeed; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /solidity/contracts/test/FullyBackedECDSAKeepStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../contracts/fully-backed/FullyBackedECDSAKeep.sol"; 4 | 5 | /// @title Fully Backed Bonded ECDSA Keep Stub 6 | /// @dev This contract is for testing purposes only. 7 | contract FullyBackedECDSAKeepStub is FullyBackedECDSAKeep { 8 | function publicMarkAsClosed() public { 9 | markAsClosed(); 10 | } 11 | 12 | function publicMarkAsTerminated() public { 13 | markAsTerminated(); 14 | } 15 | 16 | function publicSlashForSignatureFraud() public { 17 | slashForSignatureFraud(); 18 | } 19 | 20 | function isFradulentPreimageSet(bytes memory preimage) 21 | public 22 | view 23 | returns (bool) 24 | { 25 | return fraudulentPreimages[preimage]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solidity/contracts/test/LPRewardsStaker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../LPRewards.sol"; 4 | 5 | contract LPRewardsStaker { 6 | IERC20 public lpToken; 7 | LPRewards public lpRewards; 8 | 9 | constructor(IERC20 _lpToken, LPRewards _lpRewards) public { 10 | lpToken = _lpToken; 11 | lpRewards = _lpRewards; 12 | } 13 | 14 | function stake(uint256 amount) public { 15 | lpToken.approve(address(lpRewards), amount); 16 | lpRewards.stake(amount); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solidity/contracts/test/ManagedGrantStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract ManagedGrantStub { 4 | address public grantee; 5 | 6 | constructor(address _grantee) public { 7 | grantee = _grantee; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /solidity/contracts/test/RandomBeaconStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@keep-network/keep-core/contracts/IRandomBeacon.sol"; 4 | 5 | /// @title Random Beacon Service Stub 6 | /// @dev This contract is for testing purposes only. 7 | contract RandomBeaconStub is IRandomBeacon { 8 | uint256 feeEstimate = 58; 9 | uint256 entry = 0; 10 | uint256 public requestCount = 0; 11 | bool shouldFail; 12 | 13 | function requestRelayEntry() external payable returns (uint256) { 14 | return requestRelayEntry(address(0), 0); 15 | } 16 | 17 | /// @dev Get the entry fee estimate in wei for relay entry request. 18 | /// @param callbackGas Gas required for the callback. 19 | function entryFeeEstimate(uint256 callbackGas) 20 | public 21 | view 22 | returns (uint256) 23 | { 24 | return feeEstimate; 25 | } 26 | 27 | function requestRelayEntry(address callbackContract, uint256 callbackGas) 28 | public 29 | payable 30 | returns (uint256) 31 | { 32 | requestCount++; 33 | 34 | if (shouldFail) { 35 | revert("request relay entry failed"); 36 | } 37 | 38 | if (entry != 0) { 39 | callbackContract.call( 40 | abi.encodeWithSignature("__beaconCallback(uint256)", entry) 41 | ); 42 | } 43 | 44 | return requestCount; 45 | } 46 | 47 | function setEntry(uint256 newEntry) public { 48 | entry = newEntry; 49 | } 50 | 51 | function setShouldFail(bool value) public { 52 | shouldFail = value; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /solidity/contracts/test/StakingInfoStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /// @title Staking Stub 4 | /// @dev This contract is for testing purposes only. 5 | contract StakingInfoStub { 6 | // Authorized operator contracts. 7 | mapping(address => mapping(address => bool)) internal authorizations; 8 | 9 | mapping(address => address) operatorToOwner; 10 | mapping(address => address payable) operatorToBeneficiary; 11 | mapping(address => address) operatorToAuthorizer; 12 | 13 | function authorizeOperatorContract( 14 | address _operator, 15 | address _operatorContract 16 | ) public { 17 | authorizations[_operatorContract][_operator] = true; 18 | } 19 | 20 | function isAuthorizedForOperator( 21 | address _operator, 22 | address _operatorContract 23 | ) public view returns (bool) { 24 | return authorizations[_operatorContract][_operator]; 25 | } 26 | 27 | function setBeneficiary(address _operator, address payable _beneficiary) 28 | public 29 | { 30 | operatorToBeneficiary[_operator] = _beneficiary; 31 | } 32 | 33 | function beneficiaryOf(address _operator) 34 | public 35 | view 36 | returns (address payable) 37 | { 38 | return operatorToBeneficiary[_operator]; 39 | } 40 | 41 | function setAuthorizer(address _operator, address _authorizer) public { 42 | operatorToAuthorizer[_operator] = _authorizer; 43 | } 44 | 45 | function authorizerOf(address _operator) public view returns (address) { 46 | return operatorToAuthorizer[_operator]; 47 | } 48 | 49 | function setOwner(address _operator, address _owner) public { 50 | operatorToOwner[_operator] = _owner; 51 | } 52 | 53 | function ownerOf(address _operator) public view returns (address) { 54 | return operatorToOwner[_operator]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /solidity/contracts/test/TestEtherReceiver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /// @title Ether Transfer Receiver. 4 | /// @dev This contract is for testing purposes only. 5 | contract TestEtherReceiver { 6 | bool shouldFail; 7 | 8 | /// @notice Rejects ether transfers sent to this contract if the shouldFail 9 | /// flag is set to true. 10 | function() external payable { 11 | require(!shouldFail, "Payment rejected"); 12 | } 13 | 14 | function setShouldFail(bool _value) public { 15 | shouldFail = _value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /solidity/contracts/test/TestToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; 5 | 6 | contract TestToken is ERC20, ERC20Detailed { 7 | constructor() public ERC20Detailed("TEST Token", "TEST", 18) {} 8 | 9 | /// @dev Mints an amount of the token and assigns it to an account. 10 | /// Uses the internal _mint function. Anyone can call 11 | /// @param _account The account that will receive the created tokens. 12 | /// @param _amount The amount of tokens that will be created. 13 | function mint(address _account, uint256 _amount) public returns (bool) { 14 | // NOTE: this is a public function with unchecked minting. 15 | _mint(_account, _amount); 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solidity/contracts/test/TokenGrantStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract TokenGrantStub { 4 | mapping(address => address[]) granteeOperators; 5 | 6 | function getGranteeOperators(address grantee) 7 | public 8 | view 9 | returns (address[] memory) 10 | { 11 | return granteeOperators[grantee]; 12 | } 13 | 14 | function setGranteeOperator(address grantee, address operator) public { 15 | address[] memory operators = new address[](1); 16 | operators[0] = operator; 17 | granteeOperators[grantee] = operators; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /solidity/contracts/test/TokenStakingStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./StakingInfoStub.sol"; 4 | 5 | import "@keep-network/sortition-pools/contracts/api/IStaking.sol"; 6 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 7 | 8 | /// @title Token Staking Stub 9 | /// @dev This contract is for testing purposes only. 10 | contract TokenStakingStub is IStaking, StakingInfoStub { 11 | using SafeMath for uint256; 12 | 13 | uint256 public minimumStake = 200000 * 1e18; 14 | 15 | mapping(address => uint256) stakes; 16 | 17 | mapping(address => int256) public operatorLocks; 18 | 19 | address public delegatedAuthority; 20 | 21 | bool slashingShouldFail; 22 | 23 | function setSlashingShouldFail(bool _shouldFail) public { 24 | slashingShouldFail = _shouldFail; 25 | } 26 | 27 | /// @dev Sets balance variable value. 28 | function setBalance(address _operator, uint256 _balance) public { 29 | stakes[_operator] = _balance; 30 | } 31 | 32 | function balanceOf(address _address) public view returns (uint256 balance) { 33 | return stakes[_address]; 34 | } 35 | 36 | /// @dev Returns balance variable value. 37 | function eligibleStake(address _operator, address) 38 | public 39 | view 40 | returns (uint256) 41 | { 42 | return stakes[_operator]; 43 | } 44 | 45 | function slash(uint256 _amount, address[] memory _misbehavedOperators) 46 | public 47 | { 48 | if (slashingShouldFail) { 49 | // THIS SHOULD NEVER HAPPEN WITH REAL TOKEN STAKING 50 | revert("slashing failed"); 51 | } 52 | for (uint256 i = 0; i < _misbehavedOperators.length; i++) { 53 | address operator = _misbehavedOperators[i]; 54 | stakes[operator] = stakes[operator].sub(_amount); 55 | } 56 | } 57 | 58 | function lockStake(address operator, uint256 duration) public { 59 | operatorLocks[operator] = int256(duration); 60 | } 61 | 62 | function unlockStake(address operator) public { 63 | // We set it to negative value to be sure in tests that the function is 64 | // actually called and not just default `0` value is returned. 65 | operatorLocks[operator] = -1; 66 | } 67 | 68 | function claimDelegatedAuthority(address delegatedAuthoritySource) public { 69 | delegatedAuthority = delegatedAuthoritySource; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /solidity/contracts/test/integration/ExternalContractsIntegration.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@keep-network/keep-core/contracts/KeepToken.sol"; 4 | 5 | // This file contains a workaround for a truffle bug described in [truffle#1250]. 6 | // We are not able to directly require artifacts from other packages, e.g.: 7 | // `artifacts.require("@keep-network/keep-core/KeepTokenIntegration")`. 8 | // We first need to import the contracts so they are compiled and placed in 9 | // `build/contracts/` directory. 10 | // 11 | // [truffle#1250]:https://github.com/trufflesuite/truffle/issues/1250 12 | contract KeepTokenIntegration is KeepToken { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /solidity/integration/stress_test.js: -------------------------------------------------------------------------------- 1 | const BondedECDSAKeepFactory = artifacts.require("./BondedECDSAKeepFactory.sol") 2 | const BondedECDSAKeep = artifacts.require("./BondedECDSAKeep.sol") 3 | 4 | // how many iterations 5 | const iterations = 10 6 | // delay between the iterations [ms] 7 | const delay = 30000 8 | // number of keeps created at once 9 | const stressLevel = 5 10 | 11 | const groupSize = 3 12 | const threshold = 3 13 | const bond = 10 14 | const stakeLockDuration = 100 15 | 16 | module.exports = async function () { 17 | const accounts = await web3.eth.getAccounts() 18 | 19 | const keepOwner = accounts[5] 20 | const application = accounts[6] 21 | 22 | try { 23 | const keepFactory = await BondedECDSAKeepFactory.deployed() 24 | 25 | let generatedKeys = 0 26 | 27 | keepFactory.BondedECDSAKeepCreated(async (_, event) => { 28 | const keepAddress = event.returnValues.keepAddress 29 | keep = await BondedECDSAKeep.at(keepAddress) 30 | console.log( 31 | `new keep created: [${keepAddress}] at [${new Date().toLocaleString()}]` 32 | ) 33 | 34 | const publicKeyPublishedEvent = await watchPublicKeyPublished(keep) 35 | keepPublicKey = publicKeyPublishedEvent.returnValues.publicKey 36 | console.log( 37 | `public key generated for keep [${keepAddress}] at [${new Date().toLocaleString()}]: [${keepPublicKey}]` 38 | ) 39 | 40 | generatedKeys++ 41 | console.log(`generated [${generatedKeys}] public keys so far`) 42 | }) 43 | 44 | for (let i = 0; i < iterations; i++) { 45 | promises = [] 46 | for (let j = 0; j < stressLevel; j++) { 47 | promises.push(openKeep(keepFactory, keepOwner, application)) 48 | } 49 | 50 | await Promise.all(promises) 51 | await wait(delay) 52 | } 53 | } catch (err) { 54 | console.error(`unexpected failure: [${err}]`) 55 | process.exit(1) 56 | } 57 | } 58 | 59 | function openKeep(keepFactory, keepOwner, application) { 60 | return new Promise(async (resolve) => { 61 | const fee = await keepFactory.openKeepFeeEstimate.call() 62 | 63 | console.log(`opening a new keep at [${new Date().toLocaleString()}]...`) 64 | await keepFactory.openKeep( 65 | groupSize, 66 | threshold, 67 | keepOwner, 68 | bond, 69 | stakeLockDuration, 70 | { 71 | from: application, 72 | value: fee, 73 | } 74 | ) 75 | resolve() 76 | }) 77 | } 78 | 79 | function watchPublicKeyPublished(keep) { 80 | return new Promise(async (resolve) => { 81 | keep.PublicKeyPublished().on("data", (event) => { 82 | resolve(event) 83 | }) 84 | }) 85 | } 86 | 87 | function wait(ms) { 88 | return new Promise((resolve) => setTimeout(resolve, ms)) 89 | } 90 | -------------------------------------------------------------------------------- /solidity/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations") 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations) 5 | } 6 | -------------------------------------------------------------------------------- /solidity/migrations/3_initialize.js: -------------------------------------------------------------------------------- 1 | const BondedECDSAKeepVendor = artifacts.require("BondedECDSAKeepVendor") 2 | const BondedECDSAKeepVendorImplV1 = artifacts.require( 3 | "BondedECDSAKeepVendorImplV1" 4 | ) 5 | const BondedECDSAKeepFactory = artifacts.require("BondedECDSAKeepFactory") 6 | const FullyBackedECDSAKeepFactory = artifacts.require( 7 | "FullyBackedECDSAKeepFactory" 8 | ) 9 | const KeepRegistry = artifacts.require("KeepRegistry") 10 | 11 | const { contracts } = require("@keep-network/common.js") 12 | const { readExternalContractAddress } = contracts 13 | 14 | module.exports = async function (deployer) { 15 | const RegistryAddress = readExternalContractAddress( 16 | "@keep-network/keep-core", 17 | "KeepRegistry", 18 | deployer 19 | ) 20 | 21 | let registry 22 | if (process.env.TEST) { 23 | registry = await KeepRegistry.deployed() 24 | } else { 25 | registry = await KeepRegistry.at(RegistryAddress) 26 | } 27 | 28 | const proxy = await BondedECDSAKeepVendor.deployed() 29 | const vendor = await BondedECDSAKeepVendorImplV1.at(proxy.address) 30 | 31 | // The vendor implementation is being initialized as part of the vendor proxy 32 | // deployment. Here we just want to sanity check if it is initialized. 33 | if (!(await vendor.initialized())) { 34 | throw Error("vendor contract not initialized") 35 | } 36 | 37 | const factoryAddress = await vendor.selectFactory() 38 | console.log(`current factory address: [${factoryAddress}]`) 39 | 40 | // Configure registry 41 | await registry.approveOperatorContract(BondedECDSAKeepFactory.address) 42 | console.log( 43 | `approved BondedECDSAKeepFactory operator contract [${BondedECDSAKeepFactory.address}] in registry` 44 | ) 45 | 46 | await registry.approveOperatorContract(FullyBackedECDSAKeepFactory.address) 47 | console.log( 48 | `approved FullyBackedECDSAKeepFactory operator contract [${FullyBackedECDSAKeepFactory.address}] in registry` 49 | ) 50 | 51 | // Set service contract owner as operator contract upgrader by default 52 | const operatorContractUpgrader = await proxy.admin() 53 | await registry.setOperatorContractUpgrader( 54 | vendor.address, 55 | operatorContractUpgrader 56 | ) 57 | console.log( 58 | `set operator [${operatorContractUpgrader}] as [${vendor.address}] contract upgrader` 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /solidity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keep-network/keep-ecdsa", 3 | "version": "1.9.0-dev", 4 | "description": "Smart contracts for ECDSA Keep", 5 | "repository": "ssh://git@github.com/keep-network/keep-ecdsa.git", 6 | "files": [ 7 | "contracts/**/*.sol", 8 | "artifacts/" 9 | ], 10 | "scripts": { 11 | "truffle": "truffle", 12 | "clean": "rm -rf build/", 13 | "build": "truffle compile", 14 | "test": "npm run test:js", 15 | "test:js": "truffle compile && mocha --exit --recursive --timeout 75000", 16 | "test:quick": "mocha --exit --recursive --timeout 45000", 17 | "lint": "npm run lint:js && npm run lint:sol", 18 | "lint:fix": "npm run lint:fix:js && npm run lint:fix:sol", 19 | "lint:js": "eslint .", 20 | "lint:fix:js": "eslint --fix .", 21 | "lint:sol": "solium -d contracts/ && prettier --check '**/*.sol'", 22 | "lint:fix:sol": "solium -d contracts/ --fix && prettier --write '**/*.sol'" 23 | }, 24 | "author": "Satoshi Nakamoto 🤪", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/keep-network/keep-ecdsa/issues" 28 | }, 29 | "homepage": "https://github.com/keep-network/keep-ecdsa", 30 | "dependencies": { 31 | "@keep-network/keep-core": ">1.8.0-dev <1.8.0-pre", 32 | "@keep-network/sortition-pools": "1.2.0-dev.1", 33 | "@openzeppelin/upgrades": "^2.7.2", 34 | "openzeppelin-solidity": "2.3.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/cli": "^7.4.4", 38 | "@babel/core": "^7.4.5", 39 | "@babel/node": "^7.4.5", 40 | "@babel/polyfill": "^7.4.4", 41 | "@babel/preset-env": "^7.4.5", 42 | "@babel/register": "^7.4.4", 43 | "@celo/contractkit": "^1.0.1", 44 | "@keep-network/common.js": "0.0.1-3", 45 | "@openzeppelin/test-environment": "^0.1.9", 46 | "@openzeppelin/test-helpers": "^0.5.4", 47 | "@truffle/contract": "^4.1.8", 48 | "@truffle/hdwallet-provider": "^1.2.6", 49 | "bn-chai": "^1.0.1", 50 | "chai": "^4.2.0", 51 | "eslint": "^6.8.0", 52 | "eslint-config-keep": "git+https://github.com/keep-network/eslint-config-keep.git", 53 | "ethlint": "^1.2.5", 54 | "mocha": "^7.1.1", 55 | "prettier": "^2.0.2", 56 | "prettier-plugin-solidity": "^1.0.0-beta.2", 57 | "solc": "0.5.17", 58 | "solium-config-keep": "github:keep-network/solium-config-keep#0.1.2", 59 | "toml": "^3.0.0", 60 | "tomlify-j0.4": "^3.0.0", 61 | "truffle": "^5.3.1", 62 | "truffle-assertions": "^0.9.2", 63 | "truffle-plugin-verify": "^0.5.15" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /solidity/scripts/etherscan-verify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Verifying contracts on Etherscan..." 6 | 7 | npx truffle run verify \ 8 | BondedECDSAKeep \ 9 | BondedECDSAKeepFactory \ 10 | BondedECDSAKeepVendor \ 11 | BondedECDSAKeepVendorImplV1 \ 12 | BondedSortitionPoolFactory \ 13 | Branch \ 14 | ECDSARewards \ 15 | ECDSARewardsDistributor \ 16 | FullyBackedBonding \ 17 | FullyBackedECDSAKeep \ 18 | FullyBackedECDSAKeepFactory \ 19 | FullyBackedSortitionPoolFactory \ 20 | KeepBonding \ 21 | Leaf \ 22 | LPRewardsKEEPETH \ 23 | LPRewardsKEEPTBTC \ 24 | LPRewardsTBTCETH \ 25 | LPRewardsTBTCSaddle \ 26 | LPRewardsTBTCv2Saddle \ 27 | LPRewardsTBTCv2SaddleV2 \ 28 | Migrations \ 29 | Position \ 30 | StackLib \ 31 | TestToken \ 32 | --network $TRUFFLE_NETWORK 33 | -------------------------------------------------------------------------------- /solidity/scripts/generate-staker-rewards-input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script generates the input file for the staker rewards distributor for 3 | * accounts for the first four accounts. This script was written in order to 4 | * test integration between `ECDSARewardsDistributor` contract and KEEP token 5 | * dashboard with the real merkle roots data. Based on the generated file by 6 | * this script, the merkle root generator can generate merkle tree. 7 | */ 8 | 9 | const fs = require("fs") 10 | 11 | const distributorInput = 12 | "../staker-rewards/distributor/staker-reward-allocation.json" 13 | 14 | module.exports = async function () { 15 | try { 16 | const accounts = await web3.eth.getAccounts() 17 | const input = {} 18 | 19 | for (let i = 0; i < 5; i++) { 20 | const amount = web3.utils.toWei("200", "ether") 21 | input[accounts[i]] = web3.utils.numberToHex(amount).substring(2) 22 | } 23 | 24 | fs.writeFileSync(distributorInput, JSON.stringify(input, null, 2)) 25 | } catch (err) { 26 | console.error("unexpected error:", err) 27 | process.exit(1) 28 | } 29 | 30 | process.exit() 31 | } 32 | -------------------------------------------------------------------------------- /solidity/scripts/get-default-application-account.js: -------------------------------------------------------------------------------- 1 | module.exports = async function () { 2 | const accounts = await web3.eth.getAccounts() 3 | // In case we are on test network, ex. Ethereum Ropsten or Celo Alfajores, 4 | // then we operatate on one account only. Account is specified in truffle.js 5 | // For local network development, the default application account is accounts[6] 6 | if (accounts.length == 1) { 7 | console.log(`${accounts[0]}`) 8 | } else { 9 | console.log(`${accounts[6]}`) 10 | } 11 | process.exit(0) 12 | } 13 | -------------------------------------------------------------------------------- /solidity/scripts/get-network-id.js: -------------------------------------------------------------------------------- 1 | module.exports = async function () { 2 | const networkID = await web3.eth.net.getId() 3 | console.log(networkID) 4 | process.exit(0) 5 | } 6 | -------------------------------------------------------------------------------- /solidity/scripts/initialize-ecds-rewards.js: -------------------------------------------------------------------------------- 1 | // This script allocates the fake rewards for the given operator in the given 2 | // interval via stub contract. It helps test ECDSA rewards in a KEEP token 3 | // dashboard. NOTE: the `ECDSARewardsStub` contract must be deployed to network. 4 | 5 | const ECDSARewards = artifacts.require("./test/ECDSARewardsStub") 6 | const KeepToken = artifacts.require( 7 | "@keep-network/keep-core/build/truffle/KeepToken" 8 | ) 9 | const { KeepTokenAddress } = require("../migrations/external-contracts") 10 | 11 | module.exports = async function () { 12 | try { 13 | const accounts = await web3.eth.getAccounts() 14 | const keepToken = await KeepToken.at(KeepTokenAddress) 15 | const rewardsContract = await ECDSARewards.deployed() 16 | 17 | const owner = accounts[0] 18 | const totalRewards = web3.utils.toWei("180000000", "ether") 19 | 20 | await keepToken.approveAndCall( 21 | rewardsContract.address, 22 | totalRewards, 23 | "0x0", 24 | { 25 | from: owner, 26 | } 27 | ) 28 | await rewardsContract.markAsFunded({ from: owner }) 29 | 30 | // Fake rewards allocation 31 | for (let i = 1; i < 5; i++) { 32 | const operator = accounts[i] 33 | for (let interval = 0; interval <= 5; interval++) { 34 | await rewardsContract.allocateReward( 35 | operator, 36 | interval, 37 | web3.utils.toWei("3000"), 38 | { from: owner } 39 | ) 40 | } 41 | } 42 | } catch (err) { 43 | console.error("unexpected error:", err) 44 | process.exit(1) 45 | } 46 | 47 | process.exit() 48 | } 49 | -------------------------------------------------------------------------------- /solidity/scripts/initialize-ecdsa-rewards-distributor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script allocates rewards for the given merkle root (interval) based on 3 | * the `output-merkle-objects.json` generated by merkle distributor. Make sure 4 | * the file exists. You can generate this file on your own following the 5 | * `README.md` file in the `keep-ecdsa/staker-rewards` or running the 6 | * `generate-staker-rewards-input.js` script which generates a mock input for 7 | * merkle generator. This script was written to KEEP token dashboard testing 8 | * purposes. 9 | */ 10 | 11 | const fs = require("fs") 12 | const ECDSARewardsDistributor = artifacts.require("./ECDSARewardsDistributor") 13 | const KeepToken = artifacts.require( 14 | "@keep-network/keep-core/build/truffle/KeepToken" 15 | ) 16 | const { KeepTokenAddress } = require("../migrations/external-contracts") 17 | 18 | // Make sure the `output-merkle-objects.json` file exists generated by merkle distributor. 19 | const distributorOutput = 20 | "../staker-rewards/distributor/output-merkle-objects.json" 21 | 22 | module.exports = async function () { 23 | try { 24 | const accounts = await web3.eth.getAccounts() 25 | const keepToken = await KeepToken.at(KeepTokenAddress) 26 | const rewardsContract = await ECDSARewardsDistributor.deployed() 27 | if (!fs.existsSync(distributorOutput)) { 28 | throw new Error("The output file from merkle distributor doesn't exist") 29 | } 30 | 31 | const merkleJSON = JSON.parse( 32 | fs.readFileSync(distributorOutput, { encoding: "utf8" }) 33 | ) 34 | if (typeof merkleJSON !== "object") throw new Error("Invalid JSON") 35 | 36 | for (const [merkleRoot, value] of Object.entries(merkleJSON)) { 37 | const amount = web3.utils.toBN(value.tokenTotal).toString() 38 | // Allocating rewards for the given merkle root (interval). 39 | await keepToken.approve(rewardsContract.address, amount, { 40 | from: accounts[0], 41 | }) 42 | await rewardsContract.allocate(merkleRoot, amount, { from: accounts[0] }) 43 | } 44 | } catch (err) { 45 | console.error("unexpected error:", err) 46 | process.exit(1) 47 | } 48 | 49 | process.exit() 50 | } 51 | -------------------------------------------------------------------------------- /solidity/scripts/lcl-set-client-address.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Sets property of external system address to a configured `CLIENT_APP_ADDRESS` 4 | # variable value. 5 | # 6 | # Sample command: 7 | # CLIENT_APP_ADDRESS="0x2AA420Af8CB62888ACBD8C7fAd6B4DdcDD89BC82" \ 8 | # ./lcl-set-client-address.sh 9 | 10 | set -ex 11 | 12 | TBTC_SYSTEM_PROPERTY="TBTCSystemAddress" 13 | 14 | SED_SUBSTITUTION_REGEXP="['\"][a-zA-Z0-9]*['\"]" 15 | 16 | DESTINATION_FILE=$(realpath $(dirname $0)/../migrations/external-contracts.js) 17 | 18 | function update_tbtc_system_address() { 19 | sed -i -e "/${TBTC_SYSTEM_PROPERTY}/s/${SED_SUBSTITUTION_REGEXP}/\"${CLIENT_APP_ADDRESS}\"/" $DESTINATION_FILE 20 | } 21 | 22 | update_tbtc_system_address 23 | -------------------------------------------------------------------------------- /solidity/scripts/recover-bonds.js: -------------------------------------------------------------------------------- 1 | const KeepBonding = artifacts.require("KeepBonding") 2 | 3 | module.exports = async function () { 4 | try { 5 | const keepBondingAddress = process.env.KEEP_BONDING_ADDRESS 6 | if (!keepBondingAddress) { 7 | throw new Error("KEEP_BONDING_ADDRESS env not set") 8 | } 9 | 10 | const operatorPrivateKey = process.env.OPERATOR_PRIVATE_KEY 11 | if (!operatorPrivateKey) { 12 | throw new Error("OPERATOR_PRIVATE_KEY env not set") 13 | } 14 | 15 | const keepBonding = await KeepBonding.at(keepBondingAddress) 16 | 17 | const operatorAccount = web3.eth.accounts.privateKeyToAccount( 18 | operatorPrivateKey 19 | ) 20 | 21 | console.log(`withdrawing bonds for operator ${operatorAccount.address}`) 22 | 23 | const unbondedValue = await keepBonding.unbondedValue( 24 | operatorAccount.address 25 | ) 26 | if (unbondedValue.eq(web3.utils.toBN(0))) { 27 | console.log(`operator ${operatorAccount.address} has no unbonded value`) 28 | process.exit(0) 29 | } 30 | 31 | const tx = await keepBonding.withdraw.request( 32 | unbondedValue, 33 | operatorAccount.address, 34 | { 35 | from: operatorAccount.address, 36 | } 37 | ) 38 | const signedTx = (await operatorAccount.signTransaction(tx)).rawTransaction 39 | const result = await web3.eth.sendSignedTransaction(signedTx) 40 | 41 | console.log( 42 | `bonds for operator ${operatorAccount.address} have been withdrawn ` + 43 | `within transaction ${result.transactionHash}` 44 | ) 45 | } catch (e) { 46 | console.error(e) 47 | process.exit(1) 48 | } 49 | 50 | process.exit(0) 51 | } 52 | -------------------------------------------------------------------------------- /solidity/scripts/unlock-eth-accounts.js: -------------------------------------------------------------------------------- 1 | /* Here we assume that the passphrase for unlocking all the accounts on 2 | some private testnet is the same. This is intended for use with 3 | truffle. Example: 4 | 5 | KEEP_ETHEREUM_PASSWORD=password \ 6 | truffle exec ./scripts/unlock-eth-accounts.js \ 7 | --network keep_dev 8 | */ 9 | 10 | const password = process.env.KEEP_ETHEREUM_PASSWORD || "password" 11 | 12 | module.exports = async function () { 13 | const accounts = await web3.eth.getAccounts() 14 | 15 | console.log(`Total accounts: ${accounts.length}`) 16 | console.log(`---------------------------------`) 17 | 18 | for (let i = 0; i < accounts.length; i++) { 19 | const account = accounts[i] 20 | 21 | try { 22 | console.log(`\nUnlocking account: ${account}`) 23 | await web3.eth.personal.unlockAccount(account, password, 150000) 24 | console.log(`Account unlocked!`) 25 | } catch (error) { 26 | console.log(`\nAccount: ${account} not unlocked!`) 27 | console.error(error) 28 | } 29 | console.log(`\n---------------------------------`) 30 | } 31 | process.exit(0) 32 | } 33 | -------------------------------------------------------------------------------- /solidity/slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "contracts/test|node_modules/@keep-network/keep-core", 3 | "detectors_to_exclude": "assembly,timestamp,solc-version" 4 | } -------------------------------------------------------------------------------- /solidity/tenderly.yaml: -------------------------------------------------------------------------------- 1 | account_id: d9a948cf-8667-43df-acb0-ce194f6dda7a 2 | projects: 3 | thesis/keep: 4 | networks: 5 | - "1" # mainnet 6 | thesis/keep-test: 7 | - "3" # ropsten -------------------------------------------------------------------------------- /solidity/test-environment.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | accounts: { 3 | amount: 50, // Number of unlocked accounts 4 | ether: 1e6, // Initial balance of unlocked accounts (in ether) 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /solidity/test/BondedECDSAKeepVendorImplV1Test.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract } = require("@openzeppelin/test-environment") 2 | const { createSnapshot, restoreSnapshot } = require("./helpers/snapshot") 3 | 4 | const { constants, expectRevert } = require("@openzeppelin/test-helpers") 5 | 6 | const KeepRegistry = contract.fromArtifact("KeepRegistry") 7 | const BondedECDSAKeepVendorImplV1Stub = contract.fromArtifact( 8 | "BondedECDSAKeepVendorImplV1Stub" 9 | ) 10 | 11 | const chai = require("chai") 12 | const assert = chai.assert 13 | 14 | // These tests are calling BondedECDSAKeepVendorImplV1 directly. 15 | describe("BondedECDSAKeepVendorImplV1", function () { 16 | const address0 = constants.ZERO_ADDRESS 17 | const address1 = "0xF2D3Af2495E286C7820643B963FB9D34418c871d" 18 | 19 | let registry 20 | let keepVendor 21 | 22 | const implOwner = accounts[1] 23 | const upgrader = accounts[2] 24 | 25 | before(async () => { 26 | registry = await KeepRegistry.new() 27 | 28 | await registry.approveOperatorContract(address0) 29 | await registry.approveOperatorContract(address1) 30 | }) 31 | 32 | beforeEach(async () => { 33 | await createSnapshot() 34 | }) 35 | 36 | afterEach(async () => { 37 | await restoreSnapshot() 38 | }) 39 | 40 | describe("initialize", async () => { 41 | it("marks contract as initialized", async () => { 42 | keepVendor = await BondedECDSAKeepVendorImplV1Stub.new() 43 | 44 | assert.isTrue(await keepVendor.initialized()) 45 | }) 46 | }) 47 | 48 | describe("initialize", async () => { 49 | before(async () => { 50 | keepVendor = await BondedECDSAKeepVendorImplV1Stub.new({ 51 | from: implOwner, 52 | }) 53 | }) 54 | 55 | it("reverts as contract is already initialized", async () => { 56 | await expectRevert( 57 | keepVendor.initialize(address1, address1), 58 | "Contract is already initialized." 59 | ) 60 | }) 61 | }) 62 | 63 | describe("upgradeFactory", async () => { 64 | before(async () => { 65 | keepVendor = await newVendor() 66 | }) 67 | 68 | it("reverts when called directly", async () => { 69 | await expectRevert( 70 | keepVendor.upgradeFactory(address1, { from: upgrader }), 71 | "KeepRegistry address is not registered" 72 | ) 73 | }) 74 | }) 75 | 76 | describe("completeFactoryUpgrade", async () => { 77 | before(async () => { 78 | keepVendor = await newVendor() 79 | }) 80 | 81 | it("reverts when called directly", async () => { 82 | await expectRevert( 83 | keepVendor.completeFactoryUpgrade(), 84 | "Upgrade not initiated" 85 | ) 86 | }) 87 | }) 88 | 89 | async function newVendor() { 90 | const keepVendor = await BondedECDSAKeepVendorImplV1Stub.new() 91 | 92 | await registry.setOperatorContractUpgrader(keepVendor.address, upgrader) 93 | 94 | return keepVendor 95 | } 96 | }) 97 | -------------------------------------------------------------------------------- /solidity/test/helpers/generateTickets.js: -------------------------------------------------------------------------------- 1 | const { web3 } = require("@openzeppelin/test-environment") 2 | 3 | function generateTickets(randomBeaconValue, stakerValue, stakerWeight) { 4 | const tickets = [] 5 | for (let i = 1; i <= stakerWeight; i++) { 6 | const ticketValueHex = web3.utils.soliditySha3( 7 | { t: "uint", v: randomBeaconValue }, 8 | { t: "uint", v: stakerValue }, 9 | { t: "uint", v: i } 10 | ) 11 | const ticketValue = web3.utils.toBN(ticketValueHex) 12 | const ticket = { 13 | valueHex: ticketValueHex, 14 | value: ticketValue, 15 | virtualStakerIndex: i, 16 | } 17 | tickets.push(ticket) 18 | } 19 | return tickets 20 | } 21 | 22 | module.exports.generateTickets = generateTickets 23 | -------------------------------------------------------------------------------- /solidity/test/helpers/increaseTime.js: -------------------------------------------------------------------------------- 1 | const { web3 } = require("@openzeppelin/test-environment") 2 | 3 | // Increases testrpc time by the passed duration in seconds 4 | function increaseTime(duration) { 5 | const id = Date.now() 6 | 7 | return new Promise((resolve, reject) => { 8 | web3.currentProvider.send( 9 | { 10 | jsonrpc: "2.0", 11 | method: "evm_increaseTime", 12 | params: [duration], 13 | id: id, 14 | }, 15 | (err1) => { 16 | if (err1) return reject(err1) 17 | 18 | web3.currentProvider.send( 19 | { 20 | jsonrpc: "2.0", 21 | method: "evm_mine", 22 | id: id + 1, 23 | }, 24 | (err2, res) => { 25 | return err2 ? reject(err2) : resolve(res) 26 | } 27 | ) 28 | } 29 | ) 30 | }) 31 | } 32 | 33 | const duration = { 34 | seconds: function (val) { 35 | return val 36 | }, 37 | minutes: function (val) { 38 | return val * this.seconds(60) 39 | }, 40 | hours: function (val) { 41 | return val * this.minutes(60) 42 | }, 43 | days: function (val) { 44 | return val * this.hours(24) 45 | }, 46 | weeks: function (val) { 47 | return val * this.days(7) 48 | }, 49 | years: function (val) { 50 | return val * this.days(365) 51 | }, 52 | } 53 | 54 | module.exports.increaseTime = increaseTime 55 | module.exports.duration = duration 56 | -------------------------------------------------------------------------------- /solidity/test/helpers/listBalanceUtils.js: -------------------------------------------------------------------------------- 1 | const { web3 } = require("@openzeppelin/test-environment") 2 | 3 | const BN = web3.utils.BN 4 | 5 | /** 6 | * Gets a list of ETH balances from a list of addresses. 7 | * @param {list} members A list of addresses 8 | * @return {list} The list of balances in BN notation 9 | */ 10 | async function getETHBalancesFromList(members) { 11 | async function getBalance(address) { 12 | const balance = await web3.eth.getBalance(address) 13 | return new BN(balance) 14 | } 15 | return await Promise.all(members.map((address) => getBalance(address))) 16 | } 17 | 18 | /** 19 | * Gets a map of ETH balances from a list of addresses. 20 | * @param {list} members A list of addresses 21 | * @return {map} The map of balances in BN notation 22 | */ 23 | async function getETHBalancesMap(members) { 24 | async function getBalance(address) { 25 | const balance = await web3.eth.getBalance(address) 26 | return new BN(balance) 27 | } 28 | 29 | const map = {} 30 | for (let i = 0; i < members.length; i++) { 31 | const member = members[i] 32 | map[member] = await getBalance(member) 33 | } 34 | return map 35 | } 36 | 37 | /** 38 | * Gets a list of ERC20 balances given a token and a list of addresses. 39 | * @param {list} members A list of addresses 40 | * @param {Token} token ERC20 token instance 41 | * @return {list} The list of balances in BN notation 42 | */ 43 | async function getERC20BalancesFromList(members, token) { 44 | async function getBalance(address) { 45 | const balance = await token.balanceOf(address) 46 | return new BN(balance) 47 | } 48 | return await Promise.all(members.map((address) => getBalance(address))) 49 | } 50 | 51 | /** 52 | * Adds a value to every element in a list 53 | * @param {list} list A list of values 54 | * @param {number} increment The amount to add to each element 55 | * @return {list} The new list in BN notation 56 | */ 57 | function addToBalances(list, increment) { 58 | return list.map((element) => element.add(new BN(increment))) 59 | } 60 | /** 61 | * Adds a value to every entry value in a map 62 | * @param {map} map A map of values 63 | * @param {number} increment The amount to add to each element 64 | * @return {list} The new map in BN notation 65 | */ 66 | function addToBalancesMap(map, increment) { 67 | // eslint-disable-next-line guard-for-in 68 | for (const key in map) { 69 | map[key] = map[key].add(new BN(increment)) 70 | } 71 | return map 72 | } 73 | 74 | module.exports.getETHBalancesFromList = getETHBalancesFromList 75 | module.exports.getETHBalancesMap = getETHBalancesMap 76 | module.exports.getERC20BalancesFromList = getERC20BalancesFromList 77 | module.exports.addToBalances = addToBalances 78 | module.exports.addToBalancesMap = addToBalancesMap 79 | -------------------------------------------------------------------------------- /solidity/test/helpers/mineBlocks.js: -------------------------------------------------------------------------------- 1 | const { web3 } = require("@openzeppelin/test-environment") 2 | 3 | /** 4 | * Mines specific number of blocks. 5 | * @param {number} blocks Number of blocks to mine. 6 | */ 7 | async function mineBlocks(blocks) { 8 | for (let i = 0; i < blocks; i++) { 9 | web3.currentProvider.send( 10 | { 11 | jsonrpc: "2.0", 12 | method: "evm_mine", 13 | id: 12345, 14 | }, 15 | function (err, _) { 16 | if (err) console.log("Error mining a block.") 17 | } 18 | ) 19 | } 20 | } 21 | 22 | module.exports.mineBlocks = mineBlocks 23 | -------------------------------------------------------------------------------- /solidity/test/helpers/packTicket.js: -------------------------------------------------------------------------------- 1 | const { web3 } = require("@openzeppelin/test-environment") 2 | 3 | function packTicket(ticketValueHex, index, operator) { 4 | const stakerValueBytes = web3.utils.hexToBytes(operator) 5 | 6 | const ticketBytes = web3.utils.hexToBytes(ticketValueHex) 7 | const ticketValue = ticketBytes.slice(0, 8) // Take the first 8 bytes of the ticket value 8 | 9 | const virtualStakerIndexPadded = web3.utils.padLeft(index, 8) 10 | const virtualStakerIndexBytes = web3.utils.hexToBytes( 11 | virtualStakerIndexPadded 12 | ) 13 | 14 | return ticketValue.concat(stakerValueBytes).concat(virtualStakerIndexBytes) 15 | } 16 | 17 | module.exports.packTicket = packTicket 18 | -------------------------------------------------------------------------------- /solidity/test/helpers/snapshot.js: -------------------------------------------------------------------------------- 1 | // Snapshots are a feature of some EVM implementations for improved dev UX. 2 | // They allow us to snapshot the entire state of the chain, and restore it at a later point. 3 | // https://github.com/trufflesuite/ganache-core/blob/master/README.md#custom-methods 4 | const { web3 } = require("@openzeppelin/test-environment") 5 | 6 | const snapshotIdsStack = [] 7 | 8 | /** 9 | * Snapshot the state of the blockchain at the current block 10 | */ 11 | async function createSnapshot() { 12 | return await new Promise((res, rej) => { 13 | web3.currentProvider.send( 14 | { 15 | id: Math.random(), // we provide id as this is required by WS provider 16 | jsonrpc: "2.0", 17 | method: "evm_snapshot", 18 | params: [], 19 | }, 20 | function (err, result) { 21 | if (err) rej(err) 22 | const snapshotId = result.result 23 | snapshotIdsStack.push(snapshotId) 24 | res() 25 | } 26 | ) 27 | }) 28 | } 29 | 30 | /** 31 | * Restores the chain to a latest snapshot 32 | */ 33 | async function restoreSnapshot() { 34 | const snapshotId = snapshotIdsStack.pop() 35 | return await new Promise((res, rej) => { 36 | web3.currentProvider.send( 37 | { 38 | id: Math.random(), // we provide id as this is required by WS provider 39 | jsonrpc: "2.0", 40 | method: "evm_revert", 41 | params: [snapshotId], 42 | }, 43 | function (err, result) { 44 | if (err) rej(err) 45 | else res() 46 | } 47 | ) 48 | }) 49 | } 50 | 51 | module.exports.createSnapshot = createSnapshot 52 | module.exports.restoreSnapshot = restoreSnapshot 53 | -------------------------------------------------------------------------------- /solidity/test/helpers/waitForEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Resolves when a given event happens or rejects after a timeout. 3 | * @param {Event} event Contract event to watch for. 4 | * @param {number} timeout Time to wait in milliseconds (default 5000 ms). 5 | * @return {Promise} Promise which resolves when the event is seen. 6 | */ 7 | async function waitForEvent(event, timeout = 5000) { 8 | return new Promise((resolve, reject) => { 9 | const timeoutSet = setTimeout(() => { 10 | clearTimeout(timeoutSet) 11 | return reject(new Error("Timeout waiting for event")) 12 | }, timeout) 13 | 14 | event.on("data", (result) => { 15 | clearTimeout(timeoutSet) 16 | return resolve(result) 17 | }) 18 | }) 19 | } 20 | 21 | module.exports = waitForEvent 22 | -------------------------------------------------------------------------------- /solidity/test/integration/keep_signing_e2e.js: -------------------------------------------------------------------------------- 1 | const { contract, web3 } = require("@openzeppelin/test-environment") 2 | 3 | const BondedECDSAKeepFactory = contract.fromArtifact("BondedECDSAKeepFactory") 4 | const BondedECDSAKeep = contract.fromArtifact("BondedECDSAKeep") 5 | 6 | // Creates a new keep, requests signature for a digest and gets the signature 7 | // submitted to the chain. 8 | module.exports = async function () { 9 | try { 10 | const accounts = await web3.eth.getAccounts() 11 | const factory = await BondedECDSAKeepFactory.deployed() 12 | 13 | // Create new keep. 14 | const groupSize = 1 15 | const honestThreshold = 1 16 | const keepOwner = accounts[1] 17 | const bond = 1 18 | 19 | const openKeepTx = await factory 20 | .openKeep(groupSize, honestThreshold, keepOwner, bond) 21 | .catch((err) => { 22 | console.error(`failed keep creation: [${err}]`) 23 | process.exit(1) 24 | }) 25 | 26 | const keepAddress = openKeepTx.logs[0].args.keepAddress 27 | 28 | console.log("new keep created at address:", keepAddress) 29 | 30 | const keep = await BondedECDSAKeep.at(keepAddress) 31 | 32 | // Sign digest. 33 | const digest = 34 | "0xca071ca92644f1f2c4ae1bf71b6032e5eff4f78f3aa632b27cbc5f84104a32da" 35 | 36 | await requestSignature(keep, keepOwner, digest).catch((err) => { 37 | console.error(`failed to sign: [${err}]`) 38 | process.exit(1) 39 | }) 40 | process.exit(0) 41 | } catch (err) { 42 | console.error(`unexpected error: [${err}]`) 43 | process.exit(1) 44 | } 45 | } 46 | 47 | async function requestSignature(keep, keepOwner, digest) { 48 | // Register event listener to wait for an event emitted after signature is 49 | // submitted by an off-chain keep client. 50 | const eventPromise = waitForEvent(keep.SignatureSubmitted()).catch((err) => { 51 | throw new Error(`event watching failed: [${err}]`) 52 | }) 53 | 54 | console.log("signing digest:", digest) 55 | 56 | await keep.sign(digest, { from: keepOwner }).catch((err) => { 57 | throw new Error(`failed signing: [${err}]`) 58 | }) 59 | 60 | const receivedSignatureEvent = await eventPromise 61 | 62 | if (receivedSignatureEvent.returnValues.digest != digest) { 63 | throw new Error( 64 | `unexpected digest: ${receivedSignatureEvent.returnValues.digest}` 65 | ) 66 | } 67 | 68 | console.log( 69 | `received signature:\nR: ${receivedSignatureEvent.returnValues.r}\nS: ${receivedSignatureEvent.returnValues.s}` 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /solidity/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. 3 | * 4 | * More information about configuration can be found at: 5 | * truffleframework.com/docs/advanced/configuration 6 | * 7 | */ 8 | 9 | require("@babel/register") 10 | require("@babel/polyfill") 11 | const HDWalletProvider = require("@truffle/hdwallet-provider") 12 | const Kit = require("@celo/contractkit") 13 | 14 | module.exports = { 15 | /** 16 | * Networks define how you connect to your ethereum client and let you set the 17 | * defaults web3 uses to send transactions. You can ask a truffle command to 18 | * use a specific network from the command line, e.g 19 | * 20 | * $ truffle test --network 21 | */ 22 | 23 | networks: { 24 | // Useful for testing. The `development` name is special - truffle uses it by default 25 | // if it's defined here and no other network is specified at the command line. 26 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 27 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 28 | // options below to some value. 29 | // 30 | local: { 31 | host: "localhost", // Localhost (default: none) 32 | port: 8546, // Standard Ethereum port (default: none) 33 | network_id: "*", // Any network (default: none) 34 | websockets: true, // Enable EventEmitter interface for web3 (default: false) 35 | }, 36 | keep_dev: { 37 | provider: function () { 38 | return new HDWalletProvider({ 39 | privateKeys: [process.env.CONTRACT_OWNER_ACCOUNT_PRIVATE_KEY], 40 | providerOrUrl: "http://localhost:8545", 41 | }) 42 | }, 43 | gas: 6721975, 44 | network_id: 1101, 45 | }, 46 | ropsten: { 47 | provider: function () { 48 | return new HDWalletProvider({ 49 | privateKeys: [process.env.CONTRACT_OWNER_ACCOUNT_PRIVATE_KEY], 50 | providerOrUrl: process.env.CHAIN_API_URL, 51 | }) 52 | }, 53 | gas: 8000000, 54 | network_id: 3, 55 | skipDryRun: true, 56 | networkCheckTimeout: 120000, 57 | timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 58 | }, 59 | alfajores: { 60 | provider: function () { 61 | const kit = Kit.newKit(process.env.CHAIN_API_URL) 62 | kit.addAccount(process.env.CONTRACT_OWNER_ACCOUNT_PRIVATE_KEY) 63 | return kit.web3.currentProvider 64 | }, 65 | network_id: 44787, 66 | }, 67 | }, 68 | // Configure your compilers 69 | compilers: { 70 | solc: { 71 | version: "0.5.17", // Fetch exact version from solc-bin (default: truffle's version) 72 | }, 73 | }, 74 | 75 | plugins: ["truffle-plugin-verify"], 76 | 77 | api_keys: { 78 | etherscan: process.env.ETHERSCAN_API_KEY, 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /staker-rewards/.eslintignore: -------------------------------------------------------------------------------- 1 | /merkle-distributor/node_modules 2 | /distributor/node_modules -------------------------------------------------------------------------------- /staker-rewards/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-keep", "prettier"], 3 | "parserOptions": { 4 | "ecmaVersion": 2017, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "es6": true, 9 | "mocha": true 10 | }, 11 | "rules": { 12 | "new-cap": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /staker-rewards/block-by-date.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | LOG_START='\n\e[1;36m' # new line + bold + color 6 | LOG_END='\n\e[0m' # new line + reset color 7 | 8 | help() 9 | { 10 | echo "" 11 | echo "Usage: $0 --etherscan-token --timestamp " 12 | echo -e "\t--etherscan-token Etherscan API key" 13 | echo -e "\t--timestamp Timestamp of the searched block" 14 | exit 1 # Exit script after printing help 15 | } 16 | 17 | if [ "$1" == "-help" ]; then 18 | help 19 | fi 20 | 21 | # Transform long options to short ones 22 | for arg in "$@"; do 23 | shift 24 | case "$arg" in 25 | "--etherscan-token") set -- "$@" "-e" ;; 26 | "--timestamp") set -- "$@" "-t" ;; 27 | *) set -- "$@" "$arg" 28 | esac 29 | done 30 | 31 | # Parse short options 32 | OPTIND=1 33 | while getopts "e:t:" opt 34 | do 35 | case "$opt" in 36 | e ) etherscan_token="$OPTARG" ;; 37 | t ) timestamp="$OPTARG" ;; 38 | ? ) help ;; # Print help in case parameter is non-existent 39 | esac 40 | done 41 | shift $(expr $OPTIND - 1) # remove options from positional parameters 42 | 43 | #Print help in case required parameters are empty 44 | if [ -z "$etherscan_token" ] || [ -z "$timestamp" ] 45 | then 46 | echo "Some or all of the required parameters are empty"; 47 | help 48 | fi 49 | 50 | url="https://api.etherscan.io/api?\ 51 | module=block&\ 52 | action=getblocknobytime&\ 53 | timestamp=$timestamp&\ 54 | closest=after&\ 55 | apikey=$etherscan_token" 56 | 57 | block=$(curl -s $url | jq '.result|tonumber') 58 | 59 | printf "${LOG_START}Block $block${LOG_END}" -------------------------------------------------------------------------------- /staker-rewards/distributor/generate-merkle-root.ts: -------------------------------------------------------------------------------- 1 | import { parseBalanceMap } from '../merkle-distributor/src/parse-balance-map' 2 | import { program } from 'commander' 3 | import * as fs from 'fs' 4 | 5 | 6 | program 7 | .version('0.0.0') 8 | .requiredOption( 9 | '-i, --input ', 10 | 'input JSON file location containing a map of account addresses to string balances' 11 | ) 12 | 13 | program.parse(process.argv) 14 | 15 | const output_merkle_objects = './output-merkle-objects.json' 16 | 17 | // read existing merkle objects if any 18 | let merkleObjects = {} 19 | if (fs.existsSync(output_merkle_objects)) { 20 | merkleObjects = JSON.parse(fs.readFileSync(output_merkle_objects, { encoding: 'utf8' })) 21 | } 22 | 23 | // new rewards for merkle interval 24 | const json = JSON.parse(fs.readFileSync(program.input, { encoding: 'utf8' })) 25 | if (typeof json !== 'object') throw new Error('Invalid JSON') 26 | 27 | const merkleObject = parseBalanceMap(json) 28 | const totalAndClaims = { 29 | tokenTotal: merkleObject.tokenTotal, 30 | claims: merkleObject.claims 31 | } 32 | merkleObjects[merkleObject.merkleRoot] = totalAndClaims 33 | 34 | fs.writeFileSync(output_merkle_objects, JSON.stringify(merkleObjects, null, 2)) 35 | -------------------------------------------------------------------------------- /staker-rewards/distributor/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "staker-rewards", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "arg": { 8 | "version": "4.1.3", 9 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 10 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 11 | "dev": true 12 | }, 13 | "buffer-from": { 14 | "version": "1.1.1", 15 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 16 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 17 | "dev": true 18 | }, 19 | "commander": { 20 | "version": "6.2.0", 21 | "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", 22 | "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", 23 | "dev": true 24 | }, 25 | "diff": { 26 | "version": "4.0.2", 27 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 28 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 29 | "dev": true 30 | }, 31 | "make-error": { 32 | "version": "1.3.6", 33 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 34 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 35 | "dev": true 36 | }, 37 | "source-map": { 38 | "version": "0.6.1", 39 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 40 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 41 | "dev": true 42 | }, 43 | "source-map-support": { 44 | "version": "0.5.19", 45 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 46 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 47 | "dev": true, 48 | "requires": { 49 | "buffer-from": "^1.0.0", 50 | "source-map": "^0.6.0" 51 | } 52 | }, 53 | "ts-node": { 54 | "version": "8.10.2", 55 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", 56 | "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", 57 | "dev": true, 58 | "requires": { 59 | "arg": "^4.1.0", 60 | "diff": "^4.0.1", 61 | "make-error": "^1.1.1", 62 | "source-map-support": "^0.5.17", 63 | "yn": "3.1.1" 64 | } 65 | }, 66 | "yn": { 67 | "version": "3.1.1", 68 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 69 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 70 | "dev": true 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /staker-rewards/distributor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "staker-rewards", 3 | "description": "Generates merkle objects for rewards distribution", 4 | "version": "1.0.0", 5 | "keywords": [ 6 | "keep-network", 7 | "rewards" 8 | ], 9 | "scripts": { 10 | "generate-merkle-root": "ts-node generate-merkle-root.ts" 11 | }, 12 | "devDependencies": { 13 | "commander": "^6.1.0", 14 | "ts-node": "^8.5.4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /staker-rewards/lib/fraud-detector.js: -------------------------------------------------------------------------------- 1 | export default class FraudDetector { 2 | constructor(context, tokenStaking, factoryDeploymentBlock) { 3 | this.context = context 4 | 5 | this.tokenStaking = tokenStaking 6 | 7 | this.factoryDeploymentBlock = factoryDeploymentBlock 8 | } 9 | 10 | static async initialize(context) { 11 | const { TokenStaking, factoryDeploymentBlock } = context.contracts 12 | 13 | const tokenStaking = await TokenStaking.deployed() 14 | 15 | return new FraudDetector(context, tokenStaking, factoryDeploymentBlock) 16 | } 17 | 18 | async isOperatorFraudulent(operator) { 19 | if (process.env.NODE_ENV !== "test") { 20 | console.log(`Checking fraudulent activity for operator ${operator}`) 21 | } 22 | 23 | // Get all slashing events for the operator. 24 | const events = await this.tokenStaking.getPastEvents("TokensSlashed", { 25 | fromBlock: this.factoryDeploymentBlock, 26 | toBlock: "latest", 27 | filter: { operator: operator }, 28 | }) 29 | 30 | // At this moment token slashing can only be originated from tBTC application 31 | // on fraud detection. Random Beacon does not use slashing but seizing. We 32 | // assume that any slashing event is related to a fraud detection for ECDSA 33 | // Keep. 34 | // This section has to be revisited in case of implementing additional usage 35 | // of slashing function. 36 | if (events.length > 0) { 37 | return true 38 | } else { 39 | return false 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /staker-rewards/lib/numbers.js: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js" 2 | 3 | const decimalPlaces = 2 4 | const noDecimalPlaces = 0 5 | const format = { 6 | groupSeparator: "", 7 | decimalSeparator: ".", 8 | } 9 | 10 | export const shorten18Decimals = (value) => 11 | toFormat(new BigNumber(value).dividedBy(new BigNumber(1e18))) 12 | 13 | export const toFormat = (value, decimals = true, rounding) => 14 | new BigNumber(value).toFormat( 15 | decimals ? decimalPlaces : noDecimalPlaces, 16 | rounding, 17 | format 18 | ) 19 | -------------------------------------------------------------------------------- /staker-rewards/lib/tenderly.js: -------------------------------------------------------------------------------- 1 | import request from "request" 2 | 3 | export default class Tenderly { 4 | constructor(web3, options) { 5 | this.web3 = web3 6 | this.options = options 7 | } 8 | 9 | static initialize(web3, projectUrl, accessToken) { 10 | const options = { 11 | baseUrl: 12 | projectUrl || 13 | "https://api.tenderly.co/api/v1/account/thesis/project/keep", 14 | headers: { 15 | "Content-Type": "application/json", 16 | "X-Access-Key": accessToken, 17 | }, 18 | } 19 | 20 | return new Tenderly(web3, options) 21 | } 22 | 23 | async getFunctionCalls(contractAddress, functionSignature) { 24 | console.debug( 25 | `Looking for calls to contract [${contractAddress}] function [${functionSignature}]` 26 | ) 27 | 28 | const functionSelector = this.web3.eth.abi.encodeFunctionSignature( 29 | functionSignature 30 | ) 31 | 32 | const uri = `/transactions?contractId[]=eth:1:${contractAddress}&functionSelector=${functionSelector}` 33 | 34 | return new Promise((resolve, reject) => { 35 | request.get(uri, this.options, (err, res, body) => { 36 | if (err) { 37 | return reject(err) 38 | } 39 | 40 | const bodyJSON = JSON.parse(body) 41 | 42 | if (bodyJSON.error) { 43 | return reject(bodyJSON.error) 44 | } 45 | 46 | return resolve(bodyJSON) 47 | }) 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /staker-rewards/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "staker-rewards", 3 | "description": "Staker KEEP rewards", 4 | "version": "1.0.0", 5 | "type": "module", 6 | "keywords": [ 7 | "keep-network", 8 | "rewards" 9 | ], 10 | "scripts": { 11 | "test": "NODE_ENV=test node --experimental-json-modules node_modules/mocha/bin/mocha", 12 | "lint": "npm run lint:js", 13 | "lint:fix": "npm run lint:fix:js", 14 | "lint:js": "eslint .", 15 | "lint:fix:js": "eslint --fix ." 16 | }, 17 | "dependencies": { 18 | "@0x/subproviders": "^6.2.0", 19 | "@keep-network/keep-core": "^1.4.1", 20 | "@keep-network/keep-ecdsa": "1.3.1", 21 | "@keep-network/tbtc.js": "git+https://github.com/keep-network/tbtc.js.git#4da9d668f589310c2b5ff985143f27d29a7f7e61", 22 | "bignumber.js": "^9.0.1", 23 | "cli-color": "^2.0.0", 24 | "lowdb": "^1.0.0", 25 | "p-all": "^3.0.0", 26 | "request": "^2.88.2", 27 | "web3": "^1.2.7", 28 | "web3-provider-engine": "^16.0.1" 29 | }, 30 | "devDependencies": { 31 | "chai": "^4.2.0", 32 | "eslint": "^6.8.0", 33 | "eslint-config-keep": "git+https://github.com/keep-network/eslint-config-keep.git", 34 | "prettier": "^2.0.2", 35 | "mocha": "^8.2.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /staker-rewards/rewards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keep-network/keep-ecdsa/1a4a46272b827896a7ae518bdfe4b825d8d5d0f3/staker-rewards/rewards.png -------------------------------------------------------------------------------- /staker-rewards/test/data/test-transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 5 | "block_number": 999, 6 | "method": "deauthorizeSortitionPoolContract", 7 | "decoded_input": [ 8 | { 9 | "value": "0xa000000000000000000000000000000000000001" 10 | }, 11 | { 12 | "value": "0xa3748633c6786e1842b5cc44fa43db1ecc710501" 13 | } 14 | ] 15 | }, 16 | { 17 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 18 | "block_number": 1000, 19 | "method": "deauthorizeSortitionPoolContract", 20 | "decoded_input": [ 21 | { 22 | "value": "0xa000000000000000000000000000000000000002" 23 | }, 24 | { 25 | "value": "0xa3748633c6786e1842b5cc44fa43db1ecc710501" 26 | } 27 | ] 28 | }, 29 | { 30 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 31 | "block_number": 1800, 32 | "method": "deauthorizeSortitionPoolContract", 33 | "decoded_input": [ 34 | { 35 | "value": "0xa000000000000000000000000000000000000003" 36 | }, 37 | { 38 | "value": "0x735761349aD6612471487D637B526Ba44c87d158" 39 | } 40 | ] 41 | }, 42 | { 43 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 44 | "block_number": 1900, 45 | "method": "deauthorizeSortitionPoolContract", 46 | "decoded_input": [ 47 | { 48 | "value": "0xa000000000000000000000000000000000000004" 49 | }, 50 | { 51 | "value": "0xa3748633c6786e1842b5cc44fa43db1ecc710501" 52 | } 53 | ] 54 | }, 55 | { 56 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 57 | "block_number": 1950, 58 | "method": "deauthorizeSortitionPoolContract", 59 | "decoded_input": [ 60 | { 61 | "value": "0xa000000000000000000000000000000000000004" 62 | }, 63 | { 64 | "value": "0xa3748633c6786e1842b5cc44fa43db1ecc710501" 65 | } 66 | ] 67 | }, 68 | { 69 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 70 | "block_number": 2000, 71 | "method": "deauthorizeSortitionPoolContract", 72 | "decoded_input": [ 73 | { 74 | "value": "0xa000000000000000000000000000000000000005" 75 | }, 76 | { 77 | "value": "0xa3748633c6786e1842b5cc44fa43db1ecc710501" 78 | } 79 | ] 80 | }, 81 | { 82 | "to": "0x27321f84704a599ab740281e285cc4463d89a3d5", 83 | "block_number": 2001, 84 | "method": "deauthorizeSortitionPoolContract", 85 | "decoded_input": [ 86 | { 87 | "value": "0xa000000000000000000000000000000000000006" 88 | }, 89 | { 90 | "value": "0xa3748633c6786e1842b5cc44fa43db1ecc710501" 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /staker-rewards/test/helpers/blockchain.js: -------------------------------------------------------------------------------- 1 | import testBlockchain from "../data/test-blockchain.json" 2 | 3 | const getStateAtBlock = (contractAddress, blockNumber) => { 4 | const block = testBlockchain.find( 5 | (block) => block.blockNumber === blockNumber 6 | ) 7 | 8 | return ( 9 | block && 10 | block.contracts.find((contract) => contract.address === contractAddress) 11 | ) 12 | } 13 | 14 | // `inputCheck` is expected to be a function executed to match input parameters. 15 | // Matching is done based on the rules defined in `test-blockchain.json` file in 16 | // each contract's `methods` property. 17 | export function mockMethod( 18 | contractAddress, 19 | method, 20 | inputCheck, 21 | defaultOutput = "" 22 | ) { 23 | return async (_, blockNumber) => { 24 | const state = getStateAtBlock(contractAddress, blockNumber) 25 | 26 | const result = 27 | state && 28 | state.methods && 29 | state.methods[method] && 30 | state.methods[method].find(inputCheck) 31 | 32 | return (result && result.output) || defaultOutput 33 | } 34 | } 35 | 36 | export function mockEvents(contractAddress) { 37 | return async (eventName, options) => { 38 | const state = getStateAtBlock(contractAddress, options.toBlock) 39 | 40 | return state && state.events.filter((event) => event.name === eventName) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /staker-rewards/test/helpers/constants.js: -------------------------------------------------------------------------------- 1 | export const SortitionPoolAddress = "0xA3748633c6786e1842b5cC44fa43db1ecC710501" 2 | export const KeepBondingAddress = "0x27321f84704a599aB740281E285cc4463d89A3D5" 3 | export const BondedECDSAKeepFactoryAddress = 4 | "0xA7d9E842EFB252389d613dA88EDa3731512e40bD" 5 | export const TokenStakingAddress = "0x1293a54e160D1cd7075487898d65266081A15458" 6 | -------------------------------------------------------------------------------- /staker-rewards/test/helpers/mock.js: -------------------------------------------------------------------------------- 1 | import testCache from "../data/test-cache.json" 2 | import transactionsCache from "../data/test-transactions.json" 3 | import { SANCTIONED_APPLICATION_ADDRESS } from "../../lib/context.js" 4 | 5 | export const createMockContext = () => ({ 6 | cache: { 7 | getKeeps: (status) => 8 | testCache.keeps.filter((keep) => !status || keep.status.name === status), 9 | getTransactionFunctionCalls: (to, method) => 10 | transactionsCache.transactions.filter( 11 | (tx) => tx.to.toLowerCase() === to.toLowerCase() && tx.method === method 12 | ), 13 | }, 14 | contracts: { sanctionedApplicationAddress: SANCTIONED_APPLICATION_ADDRESS }, 15 | }) 16 | --------------------------------------------------------------------------------