Poll contracts

This commit is contained in:
Richard Ramos 2018-06-26 10:19:04 -04:00
parent 66b6fbf966
commit a712cece85
7 changed files with 852 additions and 3 deletions

View File

@ -48,6 +48,10 @@
"TestToken": {
"deploy": true
},
"MultiOptionPollManager": {
"deploy": false
},
"SNT": {
"instanceOf": "MiniMeToken",
"deploy": true,
@ -79,7 +83,13 @@
],
"gasLimit": 5000000
},
"PollManager": { "deploy": false }
"PollManager": {
"deploy": true,
"args": ["$MiniMeTokenFactory", "$SNT"]
},
"SingleChoice": {
"deploy": false
}
}
}
}

View File

@ -8,7 +8,7 @@ import "../token/MiniMeTokenFactory.sol";
* @title PollManager
* @author Richard Ramos (Status Research & Development GmbH)
*/
contract PollManager is Controlled {
contract MultiOptionPollManager is Controlled {
event PollCreated(uint256 pollId, uint8 numOptions);
event PollCanceled(uint256 pollId);
event Voted(address voter, uint8[] votes);

View File

@ -0,0 +1,115 @@
pragma solidity ^0.4.11;
contract LowLevelStringManipulator {
////////////////////
// Internal helper functions to manipulate strings
/////////////////
function strConcat(string _a, string _b, string _c, string _d, string _e) internal returns (string){
bytes memory _ba = bytes(_a);
bytes memory _bb = bytes(_b);
bytes memory _bc = bytes(_c);
bytes memory _bd = bytes(_d);
bytes memory _be = bytes(_e);
string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
bytes memory babcde = bytes(abcde);
uint k = 0;
for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i];
for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i];
for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i];
for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i];
for (i = 0; i < _be.length; i++) babcde[k++] = _be[i];
return string(babcde);
}
function strConcat(string _a, string _b, string _c, string _d) internal returns (string) {
return strConcat(_a, _b, _c, _d, "");
}
function strConcat(string _a, string _b, string _c) internal returns (string) {
return strConcat(_a, _b, _c, "", "");
}
function strConcat(string _a, string _b) internal returns (string) {
return strConcat(_a, _b, "", "", "");
}
function uint2str(uint a) internal returns (string) {
return bytes32ToString(uintToBytes(a));
}
function uintToBytes(uint v) internal constant returns (bytes32 ret) {
if (v == 0) {
ret = '0';
}
else {
while (v > 0) {
ret = bytes32(uint(ret) / (2 ** 8));
ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
v /= 10;
}
}
return ret;
}
function bytes32ToString (bytes32 data) internal constant returns (string) {
bytes memory bytesString = new bytes(32);
for (uint j=0; j<32; j++) {
byte char = byte(bytes32(uint(data) * 2 ** (8 * j)));
if (char != 0) {
bytesString[j] = char;
}
}
return string(bytesString);
}
function getTokenNameSymbol(address tokenAddr) internal returns (string name, string symbol) {
return (getString(tokenAddr, bytes4(sha3("name()"))),getString(tokenAddr, bytes4(sha3("symbol()"))));
}
function getString(address _dst, bytes4 sig) internal returns(string) {
string memory s;
bool success1;
bool success2;
assembly {
let x := mload(0x40) //Find empty storage location using "free memory pointer"
mstore(x,sig) //Place signature at begining of empty storage
success1 := call( //This is the critical change (Pop the top stack value)
5000, //5k gas
_dst, //To addr
0, //No value
x, //Inputs are stored at location x
0x04, //Inputs are 36 byes long
x, //Store output over input (saves space)
0x80) //Outputs are 32 bytes long
let strL := mload(add(x, 0x20)) // Load the length of the sring
jumpi(ask_more, gt(strL, 64))
mstore(0x40,add(x,add(strL, 0x40)))
s := add(x,0x20)
ask_more:
mstore(x,sig) //Place signature at begining of empty storage
success2 := call( //This is the critical change (Pop the top stack value)
5000, //5k gas
_dst, //To addr
0, //No value
x, //Inputs are stored at location x
0x04, //Inputs are 36 byes long
x, //Store output over input (saves space)
add(0x40, strL)) //Outputs are 32 bytes long
mstore(0x40,add(x,add(strL, 0x40)))
s := add(x,0x20)
}
if ((!success1)||(!success2)) throw;
return s;
}
}

View File

@ -0,0 +1,205 @@
pragma solidity ^0.4.11;
import "../common/Controlled.sol";
import "./LowLevelStringManipulator.sol";
import "../token/MiniMeToken.sol";
contract IPollContract {
function deltaVote(int _amount, bytes32 _ballot) returns (bool _succes);
function pollType() constant returns (bytes32);
function question() constant returns (string);
}
contract IPollFactory {
function create(bytes _description) returns(address);
}
contract PollManager is LowLevelStringManipulator, Controlled {
struct VoteLog {
bytes32 ballot;
uint amount;
}
struct Poll {
uint startBlock;
uint endBlock;
address token;
address pollContract;
bool canceled;
mapping(address => VoteLog) votes;
}
Poll[] _polls;
MiniMeTokenFactory public tokenFactory;
MiniMeToken public token;
function PollManager(address _tokenFactory, address _token)
public {
tokenFactory = MiniMeTokenFactory(_tokenFactory);
token = MiniMeToken(_token);
}
modifier onlySNTHolder {
// TODO: require min number of tokens?
require(token.balanceOf(msg.sender) > 0);
_;
}
function addPoll(
uint _startBlock,
uint _endBlock,
address _pollFactory,
bytes _description)
onlySNTHolder
returns (uint _idPoll)
{
if (_endBlock <= _startBlock) throw;
if (_endBlock <= getBlockNumber()) throw;
_idPoll = _polls.length;
_polls.length ++;
Poll p = _polls[ _idPoll ];
p.startBlock = _startBlock;
p.endBlock = _endBlock;
var (name,symbol) = getTokenNameSymbol(address(token));
string memory proposalName = strConcat(name , "_", uint2str(_idPoll));
string memory proposalSymbol = strConcat(symbol, "_", uint2str(_idPoll));
p.token = tokenFactory.createCloneToken(
address(token),
_startBlock - 1,
proposalName,
token.decimals(),
proposalSymbol,
true);
p.pollContract = IPollFactory(_pollFactory).create(_description);
if (p.pollContract == 0) throw;
}
function cancelPoll(uint _idPoll) onlyController {
if (_idPoll >= _polls.length) throw;
Poll p = _polls[_idPoll];
if (getBlockNumber() >= p.endBlock) throw;
p.canceled = true;
PollCanceled(_idPoll);
}
function vote(uint _idPoll, bytes32 _ballot) {
if (_idPoll >= _polls.length) throw;
Poll p = _polls[_idPoll];
if (getBlockNumber() < p.startBlock) throw;
if (getBlockNumber() >= p.endBlock) throw;
if (p.canceled) throw;
unvote(_idPoll);
uint amount = MiniMeToken(p.token).balanceOf(msg.sender);
if (amount == 0) throw;
// enableTransfers = true;
if (!MiniMeToken(p.token).transferFrom(msg.sender, address(this), amount)) throw;
// enableTransfers = false;
p.votes[msg.sender].ballot = _ballot;
p.votes[msg.sender].amount = amount;
if (!IPollContract(p.pollContract).deltaVote(int(amount), _ballot)) throw;
Vote(_idPoll, msg.sender, _ballot, amount);
}
function unvote(uint _idPoll) {
if (_idPoll >= _polls.length) throw;
Poll p = _polls[_idPoll];
if (getBlockNumber() < p.startBlock) throw;
if (getBlockNumber() >= p.endBlock) throw;
if (p.canceled) throw;
uint amount = p.votes[msg.sender].amount;
bytes32 ballot = p.votes[msg.sender].ballot;
if (amount == 0) return;
if (!IPollContract(p.pollContract).deltaVote(-int(amount), ballot)) throw;
p.votes[msg.sender].ballot = 0;
p.votes[msg.sender].amount = 0;
// enableTransfers = true;
if (!MiniMeToken(p.token).transferFrom(address(this), msg.sender, amount)) throw;
// enableTransfers = false;
Unvote(_idPoll, msg.sender, ballot, amount);
}
// Constant Helper Function
function nPolls() constant returns(uint) {
return _polls.length;
}
function poll(uint _idPoll) constant returns(
uint _startBlock,
uint _endBlock,
address _token,
address _pollContract,
bool _canceled,
bytes32 _pollType,
string _question,
bool _finalized,
uint _totalCensus
) {
if (_idPoll >= _polls.length) throw;
Poll p = _polls[_idPoll];
_startBlock = p.startBlock;
_endBlock = p.endBlock;
_token = p.token;
_pollContract = p.pollContract;
_canceled = p.canceled;
_pollType = IPollContract(p.pollContract).pollType();
_question = getString(p.pollContract, bytes4(sha3("question()")));
_finalized = (!p.canceled) && (getBlockNumber() >= _endBlock);
_totalCensus = MiniMeToken(p.token).totalSupply();
}
function getVote(uint _idPoll, address _voter) constant returns (bytes32 _ballot, uint _amount) {
if (_idPoll >= _polls.length) throw;
Poll p = _polls[_idPoll];
_ballot = p.votes[_voter].ballot;
_amount = p.votes[_voter].amount;
}
function proxyPayment(address ) payable returns(bool) {
return false;
}
function onTransfer(address , address , uint ) returns(bool) {
return true;
}
function onApprove(address , address , uint ) returns(bool) {
return true;
}
function getBlockNumber() internal constant returns (uint) {
return block.number;
}
event Vote(uint indexed idPoll, address indexed _voter, bytes32 ballot, uint amount);
event Unvote(uint indexed idPoll, address indexed _voter, bytes32 ballot, uint amount);
event PollCanceled(uint indexed idPoll);
}

416
contracts/polls/RLP.sol Normal file
View File

@ -0,0 +1,416 @@
pragma solidity ^0.4.6;
/**
* @title RLPReader
*
* RLPReader is used to read and parse RLP encoded data in memory.
*
* @author Andreas Olofsson (androlo1980@gmail.com)
*/
library RLP {
uint constant DATA_SHORT_START = 0x80;
uint constant DATA_LONG_START = 0xB8;
uint constant LIST_SHORT_START = 0xC0;
uint constant LIST_LONG_START = 0xF8;
uint constant DATA_LONG_OFFSET = 0xB7;
uint constant LIST_LONG_OFFSET = 0xF7;
struct RLPItem {
uint _unsafe_memPtr; // Pointer to the RLP-encoded bytes.
uint _unsafe_length; // Number of bytes. This is the full length of the string.
}
struct Iterator {
RLPItem _unsafe_item; // Item that's being iterated over.
uint _unsafe_nextPtr; // Position of the next item in the list.
}
/* Iterator */
function next(Iterator memory self) internal constant returns (RLPItem memory subItem) {
if(hasNext(self)) {
var ptr = self._unsafe_nextPtr;
var itemLength = _itemLength(ptr);
subItem._unsafe_memPtr = ptr;
subItem._unsafe_length = itemLength;
self._unsafe_nextPtr = ptr + itemLength;
}
else
throw;
}
function next(Iterator memory self, bool strict) internal constant returns (RLPItem memory subItem) {
subItem = next(self);
if(strict && !_validate(subItem))
throw;
return;
}
function hasNext(Iterator memory self) internal constant returns (bool) {
var item = self._unsafe_item;
return self._unsafe_nextPtr < item._unsafe_memPtr + item._unsafe_length;
}
/* RLPItem */
/// @dev Creates an RLPItem from an array of RLP encoded bytes.
/// @param self The RLP encoded bytes.
/// @return An RLPItem
function toRLPItem(bytes memory self) internal constant returns (RLPItem memory) {
uint len = self.length;
if (len == 0) {
return RLPItem(0, 0);
}
uint memPtr;
assembly {
memPtr := add(self, 0x20)
}
return RLPItem(memPtr, len);
}
/// @dev Creates an RLPItem from an array of RLP encoded bytes.
/// @param self The RLP encoded bytes.
/// @param strict Will throw if the data is not RLP encoded.
/// @return An RLPItem
function toRLPItem(bytes memory self, bool strict) internal constant returns (RLPItem memory) {
var item = toRLPItem(self);
if(strict) {
uint len = self.length;
if(_payloadOffset(item) > len)
throw;
if(_itemLength(item._unsafe_memPtr) != len)
throw;
if(!_validate(item))
throw;
}
return item;
}
/// @dev Check if the RLP item is null.
/// @param self The RLP item.
/// @return 'true' if the item is null.
function isNull(RLPItem memory self) internal constant returns (bool ret) {
return self._unsafe_length == 0;
}
/// @dev Check if the RLP item is a list.
/// @param self The RLP item.
/// @return 'true' if the item is a list.
function isList(RLPItem memory self) internal constant returns (bool ret) {
if (self._unsafe_length == 0)
return false;
uint memPtr = self._unsafe_memPtr;
assembly {
ret := iszero(lt(byte(0, mload(memPtr)), 0xC0))
}
}
/// @dev Check if the RLP item is data.
/// @param self The RLP item.
/// @return 'true' if the item is data.
function isData(RLPItem memory self) internal constant returns (bool ret) {
if (self._unsafe_length == 0)
return false;
uint memPtr = self._unsafe_memPtr;
assembly {
ret := lt(byte(0, mload(memPtr)), 0xC0)
}
}
/// @dev Check if the RLP item is empty (string or list).
/// @param self The RLP item.
/// @return 'true' if the item is null.
function isEmpty(RLPItem memory self) internal constant returns (bool ret) {
if(isNull(self))
return false;
uint b0;
uint memPtr = self._unsafe_memPtr;
assembly {
b0 := byte(0, mload(memPtr))
}
return (b0 == DATA_SHORT_START || b0 == LIST_SHORT_START);
}
/// @dev Get the number of items in an RLP encoded list.
/// @param self The RLP item.
/// @return The number of items.
function items(RLPItem memory self) internal constant returns (uint) {
if (!isList(self))
return 0;
uint b0;
uint memPtr = self._unsafe_memPtr;
assembly {
b0 := byte(0, mload(memPtr))
}
uint pos = memPtr + _payloadOffset(self);
uint last = memPtr + self._unsafe_length - 1;
uint itms;
while(pos <= last) {
pos += _itemLength(pos);
itms++;
}
return itms;
}
/// @dev Create an iterator.
/// @param self The RLP item.
/// @return An 'Iterator' over the item.
function iterator(RLPItem memory self) internal constant returns (Iterator memory it) {
if (!isList(self))
throw;
uint ptr = self._unsafe_memPtr + _payloadOffset(self);
it._unsafe_item = self;
it._unsafe_nextPtr = ptr;
}
/// @dev Return the RLP encoded bytes.
/// @param self The RLPItem.
/// @return The bytes.
function toBytes(RLPItem memory self) internal constant returns (bytes memory bts) {
var len = self._unsafe_length;
if (len == 0)
return;
bts = new bytes(len);
_copyToBytes(self._unsafe_memPtr, bts, len);
}
/// @dev Decode an RLPItem into bytes. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toData(RLPItem memory self) internal constant returns (bytes memory bts) {
if(!isData(self))
throw;
var (rStartPos, len) = _decode(self);
bts = new bytes(len);
_copyToBytes(rStartPos, bts, len);
}
/// @dev Get the list of sub-items from an RLP encoded list.
/// Warning: This is inefficient, as it requires that the list is read twice.
/// @param self The RLP item.
/// @return Array of RLPItems.
function toList(RLPItem memory self) internal constant returns (RLPItem[] memory list) {
if(!isList(self))
throw;
var numItems = items(self);
list = new RLPItem[](numItems);
var it = iterator(self);
uint idx;
while(hasNext(it)) {
list[idx] = next(it);
idx++;
}
}
/// @dev Decode an RLPItem into an ascii string. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toAscii(RLPItem memory self) internal constant returns (string memory str) {
if(!isData(self))
throw;
var (rStartPos, len) = _decode(self);
bytes memory bts = new bytes(len);
_copyToBytes(rStartPos, bts, len);
str = string(bts);
}
/// @dev Decode an RLPItem into a uint. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toUint(RLPItem memory self) internal constant returns (uint data) {
if(!isData(self))
throw;
var (rStartPos, len) = _decode(self);
if (len > 32 || len == 0)
throw;
assembly {
data := div(mload(rStartPos), exp(256, sub(32, len)))
}
}
/// @dev Decode an RLPItem into a boolean. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toBool(RLPItem memory self) internal constant returns (bool data) {
if(!isData(self))
throw;
var (rStartPos, len) = _decode(self);
if (len != 1)
throw;
uint temp;
assembly {
temp := byte(0, mload(rStartPos))
}
if (temp > 1)
throw;
return temp == 1 ? true : false;
}
/// @dev Decode an RLPItem into a byte. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toByte(RLPItem memory self) internal constant returns (byte data) {
if(!isData(self))
throw;
var (rStartPos, len) = _decode(self);
if (len != 1)
throw;
uint temp;
assembly {
temp := byte(0, mload(rStartPos))
}
return byte(temp);
}
/// @dev Decode an RLPItem into an int. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toInt(RLPItem memory self) internal constant returns (int data) {
return int(toUint(self));
}
/// @dev Decode an RLPItem into a bytes32. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toBytes32(RLPItem memory self) internal constant returns (bytes32 data) {
return bytes32(toUint(self));
}
/// @dev Decode an RLPItem into an address. This will not work if the
/// RLPItem is a list.
/// @param self The RLPItem.
/// @return The decoded string.
function toAddress(RLPItem memory self) internal constant returns (address data) {
if(!isData(self))
throw;
var (rStartPos, len) = _decode(self);
if (len != 20)
throw;
assembly {
data := div(mload(rStartPos), exp(256, 12))
}
}
// Get the payload offset.
function _payloadOffset(RLPItem memory self) private constant returns (uint) {
if(self._unsafe_length == 0)
return 0;
uint b0;
uint memPtr = self._unsafe_memPtr;
assembly {
b0 := byte(0, mload(memPtr))
}
if(b0 < DATA_SHORT_START)
return 0;
if(b0 < DATA_LONG_START || (b0 >= LIST_SHORT_START && b0 < LIST_LONG_START))
return 1;
if(b0 < LIST_SHORT_START)
return b0 - DATA_LONG_OFFSET + 1;
return b0 - LIST_LONG_OFFSET + 1;
}
// Get the full length of an RLP item.
function _itemLength(uint memPtr) private constant returns (uint len) {
uint b0;
assembly {
b0 := byte(0, mload(memPtr))
}
if (b0 < DATA_SHORT_START)
len = 1;
else if (b0 < DATA_LONG_START)
len = b0 - DATA_SHORT_START + 1;
else if (b0 < LIST_SHORT_START) {
assembly {
let bLen := sub(b0, 0xB7) // bytes length (DATA_LONG_OFFSET)
let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length
len := add(1, add(bLen, dLen)) // total length
}
}
else if (b0 < LIST_LONG_START)
len = b0 - LIST_SHORT_START + 1;
else {
assembly {
let bLen := sub(b0, 0xF7) // bytes length (LIST_LONG_OFFSET)
let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length
len := add(1, add(bLen, dLen)) // total length
}
}
}
// Get start position and length of the data.
function _decode(RLPItem memory self) private constant returns (uint memPtr, uint len) {
if(!isData(self))
throw;
uint b0;
uint start = self._unsafe_memPtr;
assembly {
b0 := byte(0, mload(start))
}
if (b0 < DATA_SHORT_START) {
memPtr = start;
len = 1;
return;
}
if (b0 < DATA_LONG_START) {
len = self._unsafe_length - 1;
memPtr = start + 1;
} else {
uint bLen;
assembly {
bLen := sub(b0, 0xB7) // DATA_LONG_OFFSET
}
len = self._unsafe_length - 1 - bLen;
memPtr = start + bLen + 1;
}
return;
}
// Assumes that enough memory has been allocated to store in target.
function _copyToBytes(uint btsPtr, bytes memory tgt, uint btsLen) private constant {
// Exploiting the fact that 'tgt' was the last thing to be allocated,
// we can write entire words, and just overwrite any excess.
assembly {
{
let i := 0 // Start at arr + 0x20
let words := div(add(btsLen, 31), 32)
let rOffset := btsPtr
let wOffset := add(tgt, 0x20)
tag_loop:
jumpi(end, eq(i, words))
{
let offset := mul(i, 0x20)
mstore(add(wOffset, offset), mload(add(rOffset, offset)))
i := add(i, 1)
}
jump(tag_loop)
end:
mstore(add(tgt, add(0x20, mload(tgt))), 0)
}
}
}
// Check that an RLP item is valid.
function _validate(RLPItem memory self) private constant returns (bool ret) {
// Check that RLP is well-formed.
uint b0;
uint b1;
uint memPtr = self._unsafe_memPtr;
assembly {
b0 := byte(0, mload(memPtr))
b1 := byte(1, mload(memPtr))
}
if(b0 == DATA_SHORT_START + 1 && b1 < DATA_SHORT_START)
return false;
return true;
}
}

View File

@ -0,0 +1,88 @@
pragma solidity ^0.4.6;
/*
Copyright 2016, Jordi Baylina
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "../common/Controlled.sol";
import "./RLP.sol";
contract SingleChoice is Controlled {
using RLP for RLP.RLPItem;
using RLP for RLP.Iterator;
using RLP for bytes;
string public question;
string[] public options;
int[] public result;
bytes32 uid;
function SingleChoice(address _controller, bytes _rlpDefinition, uint salt) {
uid = sha3(block.blockhash(block.number-1), salt);
controller = _controller;
var itmPoll = _rlpDefinition.toRLPItem(true);
if (!itmPoll.isList()) throw;
var itrPoll = itmPoll.iterator();
question = itrPoll.next().toAscii();
var itmOptions = itrPoll.next();
if (!itmOptions.isList()) throw;
var itrOptions = itmOptions.iterator();
while(itrOptions.hasNext()) {
options.length++;
options[options.length-1] = itrOptions.next().toAscii();
}
result.length = options.length;
}
function pollType() constant returns (bytes32) {
return bytes32("SINGLE_CHOICE");
}
function isValid(bytes32 _ballot) constant returns(bool) {
uint v = uint(_ballot) / (2**248);
if (v>=options.length) return false;
if (getBallot(v) != _ballot) return false;
return true;
}
function deltaVote(int _amount, bytes32 _ballot) onlyController returns (bool _succes) {
if (!isValid(_ballot)) return false;
uint v = uint(_ballot) / (2**248);
result[v] += _amount;
return true;
}
function nOptions() constant returns(uint) {
return options.length;
}
function getBallot(uint _option) constant returns(bytes32) {
return bytes32((_option * (2**248)) + (uint(sha3(uid, _option)) & (2**248 -1)));
}
}

View File

@ -0,0 +1,15 @@
pragma solidity ^0.4.6;
import "./SingleChoice.sol";
contract SingleChoiceFactory {
uint salt;
function create(bytes _description) returns(address) {
salt++;
SingleChoice sc = new SingleChoice(msg.sender, _description, salt);
SingleChoiceCreated(address(sc));
return address(sc);
}
event SingleChoiceCreated(address indexed addr);
}