use OwnableUpgradeSafe for buckets

This commit is contained in:
Andrea Franz 2020-10-01 09:51:33 +02:00
parent 65588974d5
commit af0c53e305
No known key found for this signature in database
GPG Key ID: 4F0D2F2D9DE7F29D
12 changed files with 90 additions and 27 deletions

View File

@ -1,11 +1,11 @@
pragma solidity ^0.6.1;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/cryptography/ECDSA.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol";
abstract contract Bucket {
abstract contract Bucket is OwnableUpgradeSafe {
bool initialized;
address payable public owner;
address public tokenAddress;
uint256 public expirationTime;
uint256 public startTime;
@ -34,16 +34,16 @@ abstract contract Bucket {
mapping(address => Redeemable) public redeemables;
modifier onlyOwner() {
require(msg.sender == owner, "owner required");
_;
}
constructor(bytes memory _eip712DomainName, address _tokenAddress, uint256 _startTime, uint256 _expirationTime, uint256 _maxTxDelayInBlocks) public {
initialize(_eip712DomainName, _tokenAddress, _startTime, _expirationTime, _maxTxDelayInBlocks, msg.sender);
initialize(_eip712DomainName, _tokenAddress, _startTime, _expirationTime, _maxTxDelayInBlocks);
}
function initialize(bytes memory _eip712DomainName, address _tokenAddress, uint256 _startTime, uint256 _expirationTime, uint256 _maxTxDelayInBlocks, address _owner) public {
// called by OwnableUpgradeSafe; using tx.origin otherwise msg.sender would be the factory
function _msgSender() internal view override(ContextUpgradeSafe) returns (address payable) {
return tx.origin;
}
function initialize(bytes memory _eip712DomainName, address _tokenAddress, uint256 _startTime, uint256 _expirationTime, uint256 _maxTxDelayInBlocks) public {
require(initialized == false, "already initialized");
require(_maxTxDelayInBlocks > 0 && _maxTxDelayInBlocks < 256, "the valid range is 1 to 255");
require(_expirationTime > block.timestamp, "expiration can't be in the past");
@ -52,7 +52,8 @@ abstract contract Bucket {
startTime = _startTime;
expirationTime = _expirationTime;
maxTxDelayInBlocks = _maxTxDelayInBlocks;
owner = payable(_owner);
__Ownable_init();
DOMAIN_SEPARATOR = keccak256(abi.encode(
EIP712DOMAIN_TYPEHASH,
@ -101,7 +102,7 @@ abstract contract Bucket {
function kill() external onlyOwner {
require(block.timestamp >= expirationTime, "not expired yet");
transferRedeemablesToOwner();
selfdestruct(owner);
selfdestruct(payable(owner()));
}
function getChainID() internal pure returns (uint256) {

View File

@ -2,7 +2,7 @@ pragma solidity ^0.6.1;
pragma experimental ABIEncoderV2;
import "./Bucket.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol";
contract ERC20Bucket is Bucket {
uint256 public redeemableSupply;
@ -48,7 +48,7 @@ contract ERC20Bucket is Bucket {
}
function transferRedeemablesToOwner() internal override {
bool success = IERC20(tokenAddress).transfer(owner, this.totalSupply());
bool success = IERC20(tokenAddress).transfer(owner(), this.totalSupply());
assert(success);
}

View File

@ -13,7 +13,7 @@ contract ERC20BucketFactory {
}
function create(address _tokenAddress, uint256 _startTime, uint256 _expirationTime, uint256 _maxTxDelayInBlocks) public returns (address) {
address p = address(new Proxy(abi.encodeWithSelector(0xe0c69ab8, "KeycardERC20Bucket", _tokenAddress, _startTime, _expirationTime, _maxTxDelayInBlocks, msg.sender), address(ERC20BucketImplementation)));
address p = address(new Proxy(abi.encodeWithSelector(0x185d1646, "KeycardERC20Bucket", _tokenAddress, _startTime, _expirationTime, _maxTxDelayInBlocks), address(ERC20BucketImplementation)));
emit BucketCreated(msg.sender, p);
return p;
}

View File

@ -20,6 +20,7 @@ contract NFTBucket is Bucket, IERC165, IERC721Receiver {
}
function transferRedeemablesToOwner() internal override {
address owner = owner();
IERC721(tokenAddress).setApprovalForAll(owner, true);
assert(IERC721(tokenAddress).isApprovedForAll(address(this), owner));
}
@ -34,7 +35,7 @@ contract NFTBucket is Bucket, IERC165, IERC721Receiver {
function onERC721Received(address _operator, address _from, uint256 _tokenID, bytes calldata _data) external override(IERC721Receiver) returns(bytes4) {
require(msg.sender == tokenAddress, "only the NFT contract can call this");
require((_operator == owner) || (_from == owner), "only the owner can create redeemables");
require((_operator == owner()) || (_from == owner()), "only the owner can create redeemables");
require(_data.length == 52, "invalid data field");
bytes memory d = _data;

View File

@ -13,7 +13,7 @@ contract NFTBucketFactory {
}
function create(address _tokenAddress, uint256 _startTime, uint256 _expirationTime, uint256 _maxTxDelayInBlocks) public returns (address) {
address p = address(new Proxy(abi.encodeWithSelector(0xe0c69ab8, "KeycardNFTBucket", _tokenAddress, _startTime, _expirationTime, _maxTxDelayInBlocks, msg.sender), address(NFTBucketImplementation)));
address p = address(new Proxy(abi.encodeWithSelector(0x185d1646, "KeycardNFTBucket", _tokenAddress, _startTime, _expirationTime, _maxTxDelayInBlocks), address(NFTBucketImplementation)));
emit BucketCreated(msg.sender, p);
return p;
}

View File

@ -1,6 +1,6 @@
pragma solidity >=0.5.0 <0.7.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol";
abstract contract IERC20Detailed is IERC20 {
function name() virtual public view returns (string memory);

View File

@ -1,6 +1,6 @@
pragma solidity ^0.6.1;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol";
contract StandardToken is IERC20 {

View File

@ -1,6 +1,6 @@
{
"dependencies": {
"@openzeppelin/contracts": "^3.2.0",
"@openzeppelin/contracts-ethereum-package": "^3.0.0",
"eth-gas-reporter": "^0.2.17"
}
}

View File

@ -1,6 +1,10 @@
const TestToken = artifacts.require('TestToken');
const ERC20Bucket = artifacts.require('ERC20Bucket');
const ERC20BucketFactory = artifacts.require('ERC20BucketFactory');
const {
bucketShouldBeOwnable,
factoryShouldCreateAnOwnableBucket,
} = require("./helpers");
const TOTAL_SUPPLY = 10000;
const GIFT_AMOUNT = 10;
@ -102,6 +106,9 @@ contract("ERC20Bucket", function () {
tokenInstance = new web3.eth.Contract(TestToken.abi, deployedTestToken.address);
});
bucketShouldBeOwnable("erc20", () => [ERC20Bucket, shop, tokenInstance, [START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS]]);
factoryShouldCreateAnOwnableBucket("erc20", () => [ERC20BucketFactory, ERC20Bucket, shop, tokenInstance, [START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS]]);
it("deploy factory", async () => {
const contract = new web3.eth.Contract(ERC20BucketFactory.abi);
const deploy = contract.deploy({ data: ERC20BucketFactory.bytecode });
@ -130,7 +137,7 @@ contract("ERC20Bucket", function () {
});
it("deploy bucket via factory", async () => {
const create = factoryInstance.methods.create(tokenInstance._address, START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS);
const create = factoryInstance.methods.create(tokenInstance.options.address, START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS);
const gas = await create.estimateGas();
const receipt = await create.send({
from: shop,

47
test/helpers.js Normal file
View File

@ -0,0 +1,47 @@
module.exports.bucketShouldBeOwnable = (bucketType, argsFunc) => {
it(`deploy an ownable ${bucketType} bucket`, async () => {
[bucketSpecs, deployer, tokenInstance, args] = argsFunc();
const contract = new web3.eth.Contract(bucketSpecs.abi);
const deploy = contract.deploy({
data: bucketSpecs.bytecode,
arguments: [tokenInstance.options.address, ...args],
});
const gas = await deploy.estimateGas();
const deployed = await deploy.send({
from: deployer,
gas,
});
const bucket = new web3.eth.Contract(bucketSpecs.abi, deployed.options.address);
const owner = await bucket.methods.owner().call();
assert.equal(owner, deployer);
});
}
module.exports.factoryShouldCreateAnOwnableBucket = (bucketType, argsFunc) => {
it(`factory creates an ownable ${bucketType} bucket`, async () => {
[factorySpecs, bucketSpecs, deployer, tokenInstance, args] = argsFunc();
const factoryContract = new web3.eth.Contract(factorySpecs.abi);
const deploy = factoryContract.deploy({
data: factorySpecs.bytecode,
arguments: [],
});
const deployGas = await deploy.estimateGas();
const deployed = await deploy.send({
from: deployer,
gas: deployGas,
});
const factory = new web3.eth.Contract(factorySpecs.abi, deployed.options.address);
const create = factory.methods.create(tokenInstance.options.address, ...args);
const createGas = await create.estimateGas();
const rec = await create.send({
from: deployer,
gas: createGas,
});
const bucket = new web3.eth.Contract(bucketSpecs.abi, rec.events.BucketCreated.returnValues.bucket);
const owner = await bucket.methods.owner().call();
assert.equal(owner, deployer);
});
}

View File

@ -1,6 +1,10 @@
const TestNFT = artifacts.require('TestNFT');
const NFTBucket = artifacts.require('NFTBucket');
const NFTBucketFactory = artifacts.require('NFTBucketFactory');
const {
bucketShouldBeOwnable,
factoryShouldCreateAnOwnableBucket,
} = require("./helpers");
const TOTAL_SUPPLY = 10000;
const GIFT_AMOUNT = 10;
@ -109,6 +113,9 @@ contract("NFTBucket", function () {
tokenInstance = new web3.eth.Contract(TestNFT.abi, deployedTestToken.address);
});
bucketShouldBeOwnable("nft", () => [NFTBucket, shop, tokenInstance, [START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS]]);
factoryShouldCreateAnOwnableBucket("nft", () => [NFTBucketFactory, NFTBucket, shop, tokenInstance, [START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS]]);
it("deploy factory", async () => {
const contract = new web3.eth.Contract(NFTBucketFactory.abi);
const deploy = contract.deploy({ data: NFTBucketFactory.bytecode });
@ -137,9 +144,9 @@ contract("NFTBucket", function () {
});
it("deploy bucket via factory", async () => {
const create = factoryInstance.methods.create(tokenInstance._address, START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS);
const create = factoryInstance.methods.create(tokenInstance.options.address, START_TIME, EXPIRATION_TIME, MAX_TX_DELAY_BLOCKS);
const gas = await create.estimateGas();
const receipt = await create.send({
const rec = await create.send({
from: shop,
gas: gas,
});

View File

@ -99,10 +99,10 @@
"@ethersproject/constants" "^5.0.4"
"@ethersproject/logger" "^5.0.5"
"@openzeppelin/contracts@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.0.tgz#3e6b3a7662d8ed64271ade96ef42655db983fd9d"
integrity sha512-bUOmkSoPkjnUyMiKo6RYnb0VHBk5D9KKDAgNLzF41aqAM3TeE0yGdFF5dVRcV60pZdJLlyFT/jjXIZCWyyEzAQ==
"@openzeppelin/contracts-ethereum-package@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-ethereum-package/-/contracts-ethereum-package-3.0.0.tgz#d5db971a177c3b37733db2ee4ebdb79c67575d64"
integrity sha512-Xg33RtX7FGbSK/YnroLhcGNAvH30/C84NRW8KvbSdXXYiLA8YqM1bOA9sAeLjmQxXqYUn/YL4AUVTgDnG51NOw==
"@solidity-parser/parser@^0.5.2":
version "0.5.2"