Merge pull request #6714 from ethereum/finishERC20

Finish ERC20
This commit is contained in:
chriseth 2019-05-13 10:00:38 +02:00 committed by GitHub
commit a28b6224a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 172 additions and 28 deletions

View File

@ -123,9 +123,6 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
("functionName", functionName)
.render();
solUnimplemented("require() with two parameters is not yet implemented.");
// TODO The code below is completely untested as we don't support StringLiterals yet
int const hashHeaderSize = 4;
int const byteSize = 8;
u256 const errorHash =
@ -140,20 +137,24 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
);
return Whiskers(R"(
function <functionName>(condition, message) {
function <functionName>(condition <messageVars>) {
if iszero(condition) {
let fmp := mload(<freeMemPointer>)
mstore(fmp, <errorHash>)
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>), message)
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>) <messageVars>)
revert(fmp, sub(end, fmp))
}
}
)")
("functionName", functionName)
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
("errorHash", errorHash.str())
("errorHash", formatNumber(errorHash))
("abiEncodeFunc", encodeFunc)
("hashHeaderSize", to_string(hashHeaderSize))
("messageVars",
(_messageType->sizeOnStack() > 0 ? ", " : "") +
suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
)
.render();
});
}
@ -360,6 +361,22 @@ string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits)
});
}
string YulUtilFunctions::overflowCheckedUIntSubFunction()
{
string functionName = "checked_sub_uint";
return m_functionCollector->createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
if lt(x, y) { revert(0, 0) }
diff := sub(x, y)
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();

View File

@ -86,6 +86,10 @@ public:
std::string overflowCheckedUIntAddFunction(size_t _bits);
/// @returns computes the difference between two values.
/// Assumes the input to be in range for the type.
std::string overflowCheckedUIntSubFunction();
std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required
/// to store an array in memory given its length (internally encoded, not ABI encoded).

View File

@ -111,8 +111,6 @@ string IRGenerationContext::variablePart(Expression const& _expression, size_t _
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
// TODO can we limit the generated functions to only those visited
// in the expression context? What about creation / runtime context?
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() {
Whiskers templ(R"(

View File

@ -71,6 +71,8 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
string IRGenerator::generate(ContractDefinition const& _contract)
{
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
Whiskers t(R"(
object "<CreationObject>" {
code {
@ -90,16 +92,26 @@ string IRGenerator::generate(ContractDefinition const& _contract)
)");
resetContext(_contract);
t("CreationObject", creationObjectName(_contract));
t("memoryInit", memoryInit());
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : "");
t("constructor", constructorCode(_contract));
t("deploy", deployCode(_contract));
// We generate code for all functions and rely on the optimizer to remove them again
// TODO it would probably be better to only generate functions when internalDispatch or
// virtualFunctionName is called - same below.
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
t("functions", m_context.functionCollector()->requestedFunctions());
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
return t.render();
}
@ -137,15 +149,21 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
});
}
string IRGenerator::constructorCode(FunctionDefinition const& _constructor)
string IRGenerator::constructorCode(ContractDefinition const& _contract)
{
string out;
if (!_constructor.isPayable())
out = callValueCheck();
// TODO initialize state variables in base to derived order.
// TODO base constructors
// TODO callValueCheck if there is no constructor.
if (FunctionDefinition const* constructor = _contract.constructor())
{
string out;
if (!constructor->isPayable())
out = callValueCheck();
solUnimplementedAssert(constructor->parameters().empty(), "");
return move(out) + m_context.functionName(*constructor) + "()\n";
}
solUnimplemented("Constructors are not yet implemented.");
return out;
return {};
}
string IRGenerator::deployCode(ContractDefinition const& _contract)

View File

@ -57,7 +57,7 @@ private:
/// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _function);
std::string constructorCode(FunctionDefinition const& _constructor);
std::string constructorCode(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();

View File

@ -294,17 +294,19 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
}
else
{
solUnimplementedAssert(_binOp.getOperator() == Token::Add, "");
if (IntegerType const* type = dynamic_cast<IntegerType const*>(commonType))
{
solUnimplementedAssert(!type->isSigned(), "");
defineExpression(_binOp) <<
m_utils.overflowCheckedUIntAddFunction(type->numBits()) <<
"(" <<
expressionAsType(_binOp.leftExpression(), *commonType) <<
", " <<
expressionAsType(_binOp.rightExpression(), *commonType) <<
")\n";
string left = expressionAsType(_binOp.leftExpression(), *commonType);
string right = expressionAsType(_binOp.rightExpression(), *commonType);
string fun;
if (_binOp.getOperator() == Token::Add)
fun = m_utils.overflowCheckedUIntAddFunction(type->numBits());
else if (_binOp.getOperator() == Token::Sub)
fun = m_utils.overflowCheckedUIntSubFunction();
else
solUnimplementedAssert(false, "");
defineExpression(_binOp) << fun << "(" << left << ", " << right << ")\n";
}
else
solUnimplementedAssert(false, "");
@ -375,7 +377,6 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
solAssert(!functionType->bound(), "");
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
{
// @TODO The function can very well return multiple vars.
defineExpression(_functionCall) <<
m_context.virtualFunctionName(*functionDef) <<
"(" <<
@ -385,7 +386,6 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
}
// @TODO The function can very well return multiple vars.
args = vector<string>{m_context.variable(_functionCall.expression())} + args;
defineExpression(_functionCall) <<
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
@ -973,7 +973,10 @@ string IRGeneratorForStatements::expressionAsType(Expression const& _expression,
ostream& IRGeneratorForStatements::defineExpression(Expression const& _expression)
{
return m_code << "let " << m_context.variable(_expression) << " := ";
string vars = m_context.variable(_expression);
if (!vars.empty())
m_code << "let " << move(vars) << " := ";
return m_code;
}
ostream& IRGeneratorForStatements::defineExpressionPart(Expression const& _expression, size_t _part)

View File

@ -0,0 +1,104 @@
pragma solidity ^0.5.0;
contract ERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
constructor() public {
_mint(msg.sender, 20);
}
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function transfer(address to, uint256 value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public returns (bool) {
_transfer(from, to, value);
// The subtraction here will revert on overflow.
_approve(from, msg.sender, _allowances[from][msg.sender] - value);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
// The addition here will revert on overflow.
_approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
// The subtraction here will revert on overflow.
_approve(msg.sender, spender, _allowances[msg.sender][spender] - subtractedValue);
return true;
}
function _transfer(address from, address to, uint256 value) internal {
require(to != address(0), "ERC20: transfer to the zero address");
// The subtraction and addition here will revert on overflow.
_balances[from] = _balances[from] - value;
_balances[to] = _balances[to] + value;
emit Transfer(from, to, value);
}
function _mint(address account, uint256 value) internal {
require(account != address(0), "ERC20: mint to the zero address");
// The additions here will revert on overflow.
_totalSupply = _totalSupply + value;
_balances[account] = _balances[account] + value;
emit Transfer(address(0), account, value);
}
function _burn(address account, uint256 value) internal {
require(account != address(0), "ERC20: burn from the zero address");
// The subtractions here will revert on overflow.
_totalSupply = _totalSupply - value;
_balances[account] = _balances[account] - value;
emit Transfer(account, address(0), value);
}
function _approve(address owner, address spender, uint256 value) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _burnFrom(address account, uint256 value) internal {
_burn(account, value);
_approve(account, msg.sender, _allowances[account][msg.sender] - value);
}
}
// ====
// compileViaYul: true
// ----
// totalSupply() -> 20
// transfer(address,uint256): 2, 5 -> true
// decreaseAllowance(address,uint256): 2, 0 -> true
// decreaseAllowance(address,uint256): 2, 1 -> FAILURE
// transfer(address,uint256): 2, 14 -> true
// transfer(address,uint256): 2, 2 -> FAILURE