From 119299a34721d1669927ce299eca17f56cf365f9 Mon Sep 17 00:00:00 2001 From: poma Date: Mon, 18 Jan 2021 09:28:33 -0500 Subject: [PATCH] relayer stake support --- contracts/Mocks/RelayerRegistryMock.sol | 13 +++ contracts/Mocks/TornMock.sol | 10 +++ contracts/RelayerRegistry.sol | 50 ++++++++--- migrations/1_deploy_registry.js | 10 --- package.json | 6 +- test/relayerRegistry.js | 65 -------------- test/relayerRegistry.test.js | 115 ++++++++++++++++++++++++ yarn.lock | 90 ++++++++++++++++++- 8 files changed, 264 insertions(+), 95 deletions(-) create mode 100644 contracts/Mocks/RelayerRegistryMock.sol create mode 100644 contracts/Mocks/TornMock.sol delete mode 100644 migrations/1_deploy_registry.js delete mode 100644 test/relayerRegistry.js create mode 100644 test/relayerRegistry.test.js diff --git a/contracts/Mocks/RelayerRegistryMock.sol b/contracts/Mocks/RelayerRegistryMock.sol new file mode 100644 index 0000000..4fd0b60 --- /dev/null +++ b/contracts/Mocks/RelayerRegistryMock.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.12; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../RelayerRegistry.sol"; + +contract RelayerRegistryMock is RelayerRegistry { + constructor(address _governance, IERC20 _torn) public RelayerRegistry(_governance, _torn) {} + + function resolve(bytes32 _addr) public override view returns (address) { + return address(uint160(uint256(_addr) >> (12 * 8))); + } +} diff --git a/contracts/Mocks/TornMock.sol b/contracts/Mocks/TornMock.sol new file mode 100644 index 0000000..21e27e6 --- /dev/null +++ b/contracts/Mocks/TornMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.12; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract TornMock is ERC20("TORN", "TORN") { + constructor() public { + _mint(msg.sender, 1e25); + } +} diff --git a/contracts/RelayerRegistry.sol b/contracts/RelayerRegistry.sol index 4470ed5..c405913 100644 --- a/contracts/RelayerRegistry.sol +++ b/contracts/RelayerRegistry.sol @@ -1,32 +1,54 @@ // SPDX-License-Identifier: MIT - pragma solidity ^0.6.12; -contract RelayerRegistry { +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "torn-token/contracts/ENS.sol"; + +contract RelayerRegistry is EnsResolve { address public immutable governance; + IERC20 public immutable torn; + uint256 public stake; mapping(bytes32 => bool) public isRelayer; + mapping(bytes32 => uint256) public balances; - event RelayerAdded(bytes32 indexed relayer); - event RelayerRemoved(bytes32 indexed relayer); + event RelayerAdded(bytes32 indexed relayer, uint256 stake); + event RelayerRemoved(bytes32 indexed relayer, uint256 stake); + event StakeChanged(uint256 stake); - modifier onlyGovernance() { - require(msg.sender == governance, "unauthorized"); - _; - } - - constructor(address _governance) public { + constructor(address _governance, IERC20 _torn) public { governance = _governance; + torn = _torn; } - function add(bytes32 _relayer) public onlyGovernance { + function add(bytes32 _relayer) public { + require(msg.sender == governance, "unauthorized"); require(!isRelayer[_relayer], "The relayer already exists"); + uint256 _stake = stake; + if (_stake > 0) { + address addr = resolve(_relayer); + require(torn.transferFrom(addr, address(this), _stake), "TORN stake transfer failed"); + balances[_relayer] = _stake; + } isRelayer[_relayer] = true; - emit RelayerAdded(_relayer); + emit RelayerAdded(_relayer, _stake); } - function remove(bytes32 _relayer) public onlyGovernance { + function remove(bytes32 _relayer) public { + require(msg.sender == governance, "unauthorized"); require(isRelayer[_relayer], "The relayer does not exist"); isRelayer[_relayer] = false; - emit RelayerRemoved(_relayer); + uint256 balance = balances[_relayer]; + if (balance > 0) { + balances[_relayer] = 0; + address addr = resolve(_relayer); + require(torn.transfer(addr, balance), "TORN transfer failed"); + } + emit RelayerRemoved(_relayer, balance); + } + + function setStake(uint256 _stake) public { + require(msg.sender == governance, "unauthorized"); + stake = _stake; + emit StakeChanged(_stake); } } diff --git a/migrations/1_deploy_registry.js b/migrations/1_deploy_registry.js deleted file mode 100644 index eadea5c..0000000 --- a/migrations/1_deploy_registry.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global artifacts */ -const RelayerRegistry = artifacts.require('RelayerRegistry') - -module.exports = function (deployer, network, accounts) { - return deployer.then(async () => { - const registry = await deployer.deploy(RelayerRegistry, accounts[0]) - - console.log('Registry :', registry.address) - }) -} diff --git a/package.json b/package.json index 24570d8..e79e2a4 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "homepage": "https://github.com/peppersec/solidity-template#readme", "devDependencies": { - "@openzeppelin/contracts": "^3.1.0", + "@openzeppelin/contracts": "^3.3.0", "babel-eslint": "^10.1.0", "bn-chai": "^1.0.1", "chai": "^4.2.0", @@ -44,5 +44,7 @@ "truffle-plugin-verify": "^0.3.11", "web3": "^1.2.11" }, - "dependencies": {} + "dependencies": { + "torn-token": "^1.0.0" + } } diff --git a/test/relayerRegistry.js b/test/relayerRegistry.js deleted file mode 100644 index 8b1f411..0000000 --- a/test/relayerRegistry.js +++ /dev/null @@ -1,65 +0,0 @@ -/* global artifacts, web3, contract */ -require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should() - -const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper') -const RelayerRegistry = artifacts.require('./RelayerRegistry.sol') - -contract('RelayerRegistry', (accounts) => { - let registry - let snapshotId - const relayer1 = '0x3b43172e77b9e7272c8045d818f7ce325205bed01fb56a3747b78ae9c0ce4334' - - before(async () => { - registry = await RelayerRegistry.new(accounts[0]) - snapshotId = await takeSnapshot() - }) - - describe('#add', () => { - it('should work', async () => { - const { logs } = await registry.add(relayer1) - - logs[0].event.should.be.equal('RelayerAdded') - logs[0].args.relayer.should.be.equal(relayer1) - }) - - it('should prevent double add', async () => { - await registry.add(relayer1) - await registry.add(relayer1).should.be.rejectedWith('The relayer already exists') - }) - - it('should allow readd', async () => { - await registry.add(relayer1) - await registry.remove(relayer1) - await registry.add(relayer1) - }) - - it('should prevent unauthorized access', async () => { - await registry.add(relayer1, { from: accounts[1] }).should.be.rejectedWith('unauthorized') - }) - }) - - describe('#remove', () => { - it('should work', async () => { - await registry.add(relayer1) - const { logs } = await registry.remove(relayer1) - - logs[0].event.should.be.equal('RelayerRemoved') - logs[0].args.relayer.should.be.equal(relayer1) - }) - - it('should prevent remove not existing', async () => { - await registry.remove(relayer1).should.be.rejectedWith('The relayer does not exist') - }) - - it('should prevent unauthorized access', async () => { - await registry.add(relayer1) - await registry.remove(relayer1, { from: accounts[1] }).should.be.rejectedWith('unauthorized') - }) - }) - - afterEach(async () => { - await revertSnapshot(snapshotId.result) - // eslint-disable-next-line require-atomic-updates - snapshotId = await takeSnapshot() - }) -}) diff --git a/test/relayerRegistry.test.js b/test/relayerRegistry.test.js new file mode 100644 index 0000000..1fc718a --- /dev/null +++ b/test/relayerRegistry.test.js @@ -0,0 +1,115 @@ +/* global artifacts, web3, contract */ +require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should() + +const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper') +const { toBN } = require('web3-utils') +const Torn = artifacts.require('TornMock.sol') +const RelayerRegistry = artifacts.require('RelayerRegistryMock.sol') + +contract('RelayerRegistry', (accounts) => { + let registry + let torn + let snapshotId + const relayer1 = accounts[5] + + before(async () => { + torn = await Torn.new() + await torn.transfer(accounts[5], '1000') + registry = await RelayerRegistry.new(accounts[0], torn.address) + snapshotId = await takeSnapshot() + }) + + describe('#add', () => { + it('should work', async () => { + const { logs } = await registry.add(relayer1) + + logs[0].event.should.be.equal('RelayerAdded') + logs[0].args.relayer.should.be.equal((relayer1 + '000000000000000000000000').toLowerCase()) + }) + + it('should prevent double add', async () => { + await registry.add(relayer1) + await registry.add(relayer1).should.be.rejectedWith('The relayer already exists') + }) + + it('should allow readd', async () => { + await registry.add(relayer1) + await registry.remove(relayer1) + await registry.add(relayer1) + }) + + it('should charge tokens', async () => { + const relayerBalanceBefore = await torn.balanceOf(relayer1) + relayerBalanceBefore.should.eq.BN(toBN(1000)) + const registryBalanceBefore = await torn.balanceOf(registry.address) + registryBalanceBefore.should.eq.BN(toBN(0)) + + const stake = toBN(1000) + await torn.approve(registry.address, stake, { from: relayer1 }) + await registry.setStake(stake) + await registry.add(relayer1) + + const relayerBalanceAfter = await torn.balanceOf(relayer1) + relayerBalanceAfter.should.eq.BN(toBN(0)) + const registryBalanceAfter = await torn.balanceOf(registry.address) + registryBalanceAfter.should.eq.BN(toBN(1000)) + }) + + it('should fail when can not charge tokens', async () => { + await registry.setStake(toBN(1000)) + await registry.add(relayer1).should.be.rejectedWith('ERC20: transfer amount exceeds allowance.') + }) + + it('should prevent unauthorized access', async () => { + await registry.add(relayer1, { from: accounts[1] }).should.be.rejectedWith('unauthorized') + }) + }) + + describe('#remove', () => { + it('should work', async () => { + await registry.add(relayer1) + const { logs } = await registry.remove(relayer1) + + logs[0].event.should.be.equal('RelayerRemoved') + logs[0].args.relayer.should.be.equal((relayer1 + '000000000000000000000000').toLowerCase()) + }) + + it('should prevent remove not existing', async () => { + await registry.remove(relayer1).should.be.rejectedWith('The relayer does not exist') + }) + + it('should return tokens') + it('should return correct token amount after stake change') + it('should allow relayer to initiate exit') + + it('should prevent unauthorized access', async () => { + await registry.add(relayer1) + await registry.remove(relayer1, { from: accounts[1] }).should.be.rejectedWith('unauthorized') + }) + }) + + describe('#setStake', () => { + it('should work', async () => { + const stakeBefore = await registry.stake() + stakeBefore.should.eq.BN(0) + + const { logs } = await registry.setStake(toBN(1000)) + + logs[0].event.should.be.equal('StakeChanged') + logs[0].args.stake.should.be.eq.BN(toBN(1000)) + + const stakeAfter = await registry.stake() + stakeAfter.should.eq.BN(toBN(1000)) + }) + + it('should prevent unauthorized access', async () => { + await registry.setStake('1', { from: accounts[1] }).should.be.rejectedWith('unauthorized') + }) + }) + + afterEach(async () => { + await revertSnapshot(snapshotId.result) + // eslint-disable-next-line require-atomic-updates + snapshotId = await takeSnapshot() + }) +}) diff --git a/yarn.lock b/yarn.lock index ffc269d..98d23fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -236,6 +236,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.0.tgz#3e6b3a7662d8ed64271ade96ef42655db983fd9d" integrity sha512-bUOmkSoPkjnUyMiKo6RYnb0VHBk5D9KKDAgNLzF41aqAM3TeE0yGdFF5dVRcV60pZdJLlyFT/jjXIZCWyyEzAQ== +"@openzeppelin/contracts@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.3.0.tgz#ffdb693c5c349fc33bba420248dd3ac0a2d7c408" + integrity sha512-AemZEsQYtUp1WRkcmZm1div5ORfTpLquLaziCIrSagjxyKdmObxuaY1yjQ5SHFMctR8rLwp706NXTbiIRJg7pw== + "@resolver-engine/core@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.2.1.tgz#0d71803f6d3b8cb2e9ed481a1bf0ca5f5256d0c0" @@ -585,12 +590,12 @@ bn.js@4.11.8: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.9, bn.js@^4.4.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.9, bn.js@^4.4.0, bn.js@^4.8.0: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== -bn.js@^5.1.1: +bn.js@^5.1.1, bn.js@^5.1.2: version "5.1.3" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== @@ -1643,6 +1648,18 @@ eth-lib@^0.1.26: ws "^3.0.0" xhr-request-promise "^0.1.2" +eth-sig-util@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.5.3.tgz#6938308b38226e0b3085435474900b03036abcbe" + integrity sha512-KpXbCKmmBUNUTGh9MRKmNkIPietfhzBqqYqysDavLseIiMUGl95k6UcPEkALAZlj41e9E6yioYXc1PC333RKqw== + dependencies: + buffer "^5.2.1" + elliptic "^6.4.0" + ethereumjs-abi "0.6.5" + ethereumjs-util "^5.1.1" + tweetnacl "^1.0.0" + tweetnacl-util "^0.15.0" + ethereum-bloom-filters@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.7.tgz#b7b80735e385dbb7f944ce6b4533e24511306060" @@ -1671,6 +1688,14 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" +ethereumjs-abi@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" + integrity sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE= + dependencies: + bn.js "^4.10.0" + ethereumjs-util "^4.3.0" + ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz#2065dbe9214e850f2e955a80e650cb6999066979" @@ -1684,6 +1709,30 @@ ethereumjs-tx@^2.1.1: ethereumjs-common "^1.5.0" ethereumjs-util "^6.0.0" +ethereumjs-util@^4.3.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz#f4bf9b3b515a484e3cc8781d61d9d980f7c83bd0" + integrity sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w== + dependencies: + bn.js "^4.8.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + rlp "^2.0.0" + +ethereumjs-util@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz#a833f0e5fca7e5b361384dc76301a721f537bf65" + integrity sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ== + dependencies: + bn.js "^4.11.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + ethjs-util "^0.1.3" + rlp "^2.0.0" + safe-buffer "^5.1.1" + ethereumjs-util@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69" @@ -1697,6 +1746,18 @@ ethereumjs-util@^6.0.0: ethjs-util "0.1.6" rlp "^2.2.3" +ethereumjs-util@^7.0.3: + version "7.0.7" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.7.tgz#484fb9c03b766b2ee64821281070616562fb5a59" + integrity sha512-vU5rtZBlZsgkTw3o6PDKyB8li2EgLavnAbsKcfsH2YhHH1Le+PP8vEiMnAnvgc1B6uMoaM5GDCrVztBw0Q5K9g== + dependencies: + "@types/bn.js" "^4.11.3" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" + rlp "^2.2.4" + ethers@4.0.0-beta.3: version "4.0.0-beta.3" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.0-beta.3.tgz#15bef14e57e94ecbeb7f9b39dd0a4bd435bc9066" @@ -1721,7 +1782,7 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" -ethjs-util@0.1.6: +ethjs-util@0.1.6, ethjs-util@^0.1.3: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== @@ -3510,7 +3571,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rlp@^2.2.3: +rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4: version "2.2.6" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.6.tgz#c80ba6266ac7a483ef1e69e8e2f056656de2fb2c" integrity sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg== @@ -3981,6 +4042,17 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +torn-token@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/torn-token/-/torn-token-1.0.0.tgz#73e47025646c3e07ab733afbf7aa53b6ab9cad55" + integrity sha512-BRzt3fy4211jCb64cWyOb5/NMGt9M9EV2ZFnGQYMubKrVkszoYm97Nex+EBa0mvV2KfXzCmJ6tehoDgOFAPGug== + dependencies: + "@openzeppelin/contracts" "^3.1.0" + dotenv "^8.2.0" + eth-sig-util "^2.5.3" + ethereumjs-util "^7.0.3" + web3 "^1.2.11" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -4042,11 +4114,21 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tweetnacl-util@^0.15.0: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +tweetnacl@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"