diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e80ffd8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts diff --git a/foundry.toml b/foundry.toml index 4ff40c4..d3dc103 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,3 @@ [profile.default] -src = "src" -out = "out" -libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file +libs = ['node_modules', 'lib'] +test = 'test' \ No newline at end of file diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..74cfb77 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..e50c24f --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit e50c24f5839db17f46991478384bfda14acfb830 diff --git a/script/Counter.s.sol b/script/Bootstrap.s.sol similarity index 100% rename from script/Counter.s.sol rename to script/Bootstrap.s.sol diff --git a/src/Bootstrap.sol b/src/Bootstrap.sol new file mode 100644 index 0000000..74be2cc --- /dev/null +++ b/src/Bootstrap.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "openzeppelin-contracts/contracts/access/Ownable.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +contract Bootstrap is Ownable { + IERC20 public immutable token; + + uint256 public immutable rewardRate; + + mapping(uint8 => mapping(uint256 => bytes)) enrIndex; + + mapping(uint8 => uint256) shardEnrIndex; + + mapping(uint8 => mapping(uint256 => mapping(bytes => bool))) public shardEnrs; + + uint8 public immutable shardCount; + + mapping(address => bool) public arbiters; + + struct Challenge { + uint256 uptime; + address arbiter; + } + + modifier onlyArbiter() { + require(arbiters[msg.sender] == true, "Arbiter: you aren't"); + _; + } + + // map epoch => shard => enr => uptime struct + mapping(uint256 => mapping(uint8 => mapping(uint256 => mapping(bytes => Challenge)))) public challenges; + + event NewEnr(uint8 shardId, bytes enr); + event RemovedEnr(uint8 shardId, bytes enr, Challenge challenge); + + constructor(address _token, uint8 _shardCount, address[] memory _arbiters, uint256 _rewardRate) { + token = IERC20(_token); + shardCount = _shardCount; + for (uint8 i = 0; i < _arbiters.length; ++i) { + arbiters[_arbiters[i]] = true; + } + rewardRate = _rewardRate; + } + + function registerEnr(uint8 shard, bytes memory enr) external { + require(shard <= shardCount, "shard invalid"); + + shardEnrs[shard][shardEnrIndex[shard]][enr] = true; + ++shardEnrIndex[shard]; + + emit NewEnr(shard, enr); + } + + function getEnrForShard(uint8 shard) public view returns (bytes[] memory) { + uint256 shardSize = shardEnrIndex[shard]; + bytes[] memory t = new bytes[](shardSize); + for (uint256 i = 0; i < shardSize; i++) { + bytes memory enr = enrIndex[shard][i]; + t[i] = enr; + } + return t; + } + + function challengeEnrUptime(uint8 shard, bytes memory enr, uint256 uptime) public onlyArbiter { + uint256 epoch = block.number % 10; // todo define epochs + challenges[epoch][shard][shardEnrIndex[shard]][enr] = Challenge({uptime: uptime, arbiter: msg.sender}); + } + + function removeEnr(uint256 epoch, uint8 shard, bytes memory enr) public onlyOwner { + Challenge storage c = challenges[epoch][shard][shardEnrIndex[shard]][enr]; + require(c.uptime >= 0, "RemoveEnr: 0 uptime"); + require(c.arbiter != address(0), "RemoveEnr: 0 address"); + + require(token.balanceOf(address(this)) >= rewardRate, "RemoveEnr: no balance"); + + shardEnrs[shard][shardEnrIndex[shard]][enr] = false; + emit RemovedEnr(shard, enr, c); + + // add balance to arbiter's + token.transfer(c.arbiter, rewardRate); + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/test/Bootstrap.t.sol b/test/Bootstrap.t.sol new file mode 100644 index 0000000..27896e0 --- /dev/null +++ b/test/Bootstrap.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "forge-std/Test.sol"; +import "../src/Bootstrap.sol"; + +contract BootstrapTest is Test { + ERC20 public token; + Bootstrap public bootstrap; + + function setUp() public { + token = new ERC20("bootstrap_test", "BOOT"); + bootstrap = new Bootstrap(address(token)); + } + + function testSanity() public { + assertEq(address(this), bootstrap.owner()); + assertEq(address(token), address(bootstrap.token())); + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 30235e8..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function testIncrement() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testSetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -}