mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
commit
a28b6224a4
@ -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();
|
||||||
|
@ -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).
|
||||||
|
@ -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"(
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
104
test/libsolidity/semanticTests/viaYul/erc20.sol
Normal file
104
test/libsolidity/semanticTests/viaYul/erc20.sol
Normal 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
|
Loading…
Reference in New Issue
Block a user