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) ("functionName", functionName)
.render(); .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 hashHeaderSize = 4;
int const byteSize = 8; int const byteSize = 8;
u256 const errorHash = u256 const errorHash =
@ -140,20 +137,24 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
); );
return Whiskers(R"( return Whiskers(R"(
function <functionName>(condition, message) { function <functionName>(condition <messageVars>) {
if iszero(condition) { if iszero(condition) {
let fmp := mload(<freeMemPointer>) let fmp := mload(<freeMemPointer>)
mstore(fmp, <errorHash>) mstore(fmp, <errorHash>)
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>), message) let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>) <messageVars>)
revert(fmp, sub(end, fmp)) revert(fmp, sub(end, fmp))
} }
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer)) ("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
("errorHash", errorHash.str()) ("errorHash", formatNumber(errorHash))
("abiEncodeFunc", encodeFunc) ("abiEncodeFunc", encodeFunc)
("hashHeaderSize", to_string(hashHeaderSize)) ("hashHeaderSize", to_string(hashHeaderSize))
("messageVars",
(_messageType->sizeOnStack() > 0 ? ", " : "") +
suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
)
.render(); .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 YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{ {
string functionName = "array_length_" + _type.identifier(); string functionName = "array_length_" + _type.identifier();

View File

@ -86,6 +86,10 @@ public:
std::string overflowCheckedUIntAddFunction(size_t _bits); 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); std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required /// @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). /// 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) 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); string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() { return m_functions->createFunction(funName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(

View File

@ -71,6 +71,8 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
string IRGenerator::generate(ContractDefinition const& _contract) string IRGenerator::generate(ContractDefinition const& _contract)
{ {
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
Whiskers t(R"( Whiskers t(R"(
object "<CreationObject>" { object "<CreationObject>" {
code { code {
@ -90,16 +92,26 @@ string IRGenerator::generate(ContractDefinition const& _contract)
)"); )");
resetContext(_contract); resetContext(_contract);
t("CreationObject", creationObjectName(_contract)); t("CreationObject", creationObjectName(_contract));
t("memoryInit", memoryInit()); t("memoryInit", memoryInit());
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : ""); t("constructor", constructorCode(_contract));
t("deploy", deployCode(_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()); t("functions", m_context.functionCollector()->requestedFunctions());
resetContext(_contract); resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("RuntimeObject", runtimeObjectName(_contract)); t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_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()); t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
return t.render(); 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; // TODO initialize state variables in base to derived order.
if (!_constructor.isPayable()) // TODO base constructors
out = callValueCheck(); // 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 {};
return out;
} }
string IRGenerator::deployCode(ContractDefinition const& _contract) string IRGenerator::deployCode(ContractDefinition const& _contract)

View File

@ -57,7 +57,7 @@ private:
/// Generates code for and returns the name of the function. /// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _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 deployCode(ContractDefinition const& _contract);
std::string callValueCheck(); std::string callValueCheck();

View File

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