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)
|
||||
.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();
|
||||
|
@ -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).
|
||||
|
@ -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"(
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
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