Skip to content

Commit

Permalink
Merge pull request #29 from tonlabs/0.36
Browse files Browse the repository at this point in the history
0.36
  • Loading branch information
BorisI authored Feb 4, 2021
2 parents 81006a8 + c11e089 commit 5914224
Show file tree
Hide file tree
Showing 23 changed files with 2,135 additions and 190 deletions.
90 changes: 79 additions & 11 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ contract development.
* [\<TvmSlice\>.loadSigned()](#tvmsliceloadsigned)
* [\<TvmSlice\>.loadUnsigned()](#tvmsliceloadunsigned)
* [\<TvmSlice\>.loadTons()](#tvmsliceloadtons)
* [\<TvmSlice\>.loadSlice()](#tvmsliceloadslice)
* [\<TvmSlice\>.decodeFunctionParams()](#tvmslicedecodefunctionparams)
* [TvmBuilder](#tvmbuilder)
* [\<TvmBuilder\>.toSlice()](#tvmbuildertoslice)
Expand Down Expand Up @@ -175,6 +176,7 @@ contract development.
* [math.divr() math.divc()](#mathdivr-mathdivc)
* [math.muldiv() math.muldivr() math.muldivc()](#mathmuldiv-mathmuldivr-mathmuldivc)
* [math.muldivmod()](#mathmuldivmod)
* [math.divmod()](#mathdivmod)
* [**tx** namespace](#tx-namespace)
* [tx.timestamp](#txtimestamp)
* [**block** namespace](#block-namespace)
Expand Down Expand Up @@ -379,6 +381,14 @@ Loads an unsigned integer with the given **bitSize** from the slice.

Loads (deserializes) **VarUInteger 16** and returns an unsigned 128-bit integer. See [TL-B scheme][3].

##### \<TvmSlice\>.loadSlice()

```TVMSolidity
<TvmSlice>.loadSlice(uint16 length) returns (TvmSlice);
```

Loads the first `length` bits from the slice into a separate slice.

##### \<TvmSlice\>.decodeFunctionParams()

```TVMSolidity
Expand Down Expand Up @@ -831,7 +841,14 @@ format(string template, TypeA a, TypeB b, ...) returns (string);
```

Builds a string with arbitrary parameters. Empty placeholder {} can be filled with integer
(in decimal view) or address. The only specified format is {:x} to fill with integer in a hexadecimal view.
(in decimal view), address or string.
Placeholder should be specified in such formats:

* "{}" - empty placeholder
* "{:[0]<width>{"x","d"}}" - placeholder for integers. Fills num with 0 if format starts with "0".
Formats integer to have specified `width`. Can format integers in decimal ("d" postfix) or hex ("x")
form.

**Note**\: total length of the string shouldn't exceed 127.
Warning: this function consumes too much gas, that's why it's better not to use it onchain.
Example:
Expand All @@ -841,6 +858,14 @@ string str = format("Hello {} 0x{:x} {} {}.{} tons", 123, 255, address.makeAddr
require(str == "Hello 123 0xFF -21:7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF123456789ABCDE 100500.32 tons", 101);
require(format("Hello {}", 123) == "Hello 123", 102);
require(format("Hello 0x{:x}", 123) == "Hello 0x7B", 103);
require(format("{}", -123) == "-123", 103);
require(format("{}", address.makeAddrStd(127,0)) == "7F:0000000000000000000000000000000000000000000000000000000000000000", 104);
require(format("{}", address.makeAddrStd(-128,0)) == "-80:0000000000000000000000000000000000000000000000000000000000000000", 105);
require(format("{:6}", 123) == " 123", 106);
require(format("{:06}", 123) == "000123", 107);
require(format("{:06d}", 123) == "000123", 108);
require(format("{:06x}", 123) == "00007B", 109);
require(format("{:6x}", 123) == " 7B", 110);
```

#### stoi()
Expand Down Expand Up @@ -2378,20 +2403,40 @@ tvm.buildExtMsg({
expire: uint32,
call: {functionIdentifier [, list of function arguments]},
sign: bool,
pubkey: optional(uint256)
pubkey: optional(uint256),
abiVer: uint8,
callbackId: uint32.
onErrorId: uint32,
stateInit: TvmCell
})
returns (TvmCell);
```

Function allows to create an external inbound message, that calls the **func** function of the
contract on address **destination** with specified function arguments. Other function parameters
define the fields of the message:
Allows to create an external inbound message, that calls the **func** function of the
contract on address **destination** with specified function arguments.
Mandatory parameters that are used to form a src address field that is used for debots:

* `abiVer` - ABI version.
* `callbackId` - identifier of the callback function.
* `onErrorId` - identifier of the function that is called in case of error.

This parameters are stored in addr_extern and placed to the src field of the message.
Message is of type [ext_in_msg_info](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L127) and src addr is of type [addr_extern](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L101) but stores special data:

* callback id - 32 bits;
* on error id - 32 bits;
* abi version - 8 bits;
* header mask - 3 bits in such order: time, expire, pubkey.

Other function parameters define fields of the message:

* `time` - message creation timestamp. Used for replay attack protection, encoded as 64 bit Unix time in milliseconds.
* `expire` - Unix time (in seconds, 32 bit) after that message should not be processed by contract.
* `pubkey` - public key from key pair used for signing the message body. This parameter is optional and can be omitted.
* `sign` - constant bool flag that shows whether message should contain signature. If set to **true** message is generated with signature field filled with zeroes. This parameter is optional and can be omitted (in this case is equal to **false**).

Also user can attach stateInit to the message using `stateInit` parameter.

Function throws an exception with code 64 if function is called with wrong parameters (pubkey is set and has value, but sign is false or omitted).

Example:
Expand All @@ -2404,24 +2449,27 @@ interface Foo {
contract Test {
TvmCell public m_cell;
function generate0() public {
tvm.accept();
address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123);
m_cell = tvm.buildExtMsg({dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 123, 45}});
m_cell = tvm.buildExtMsg({abiVer: 1, callbackId: 0, onErrorId: 0, dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 111, 88}});
}
function generate1() public {
tvm.accept();
optional(uint) pubkey;
address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123);
m_cell = tvm.buildExtMsg({dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 123, 45}, pubkey: pubkey});
m_cell = tvm.buildExtMsg({abiVer: 1, callbackId: 0, onErrorId: 0, dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 111, 88}, pubkey: pubkey});
}
function generate2() public {
tvm.accept();
optional(uint) pubkey = 0x95c06aa743d1f9000dd64b75498f106af4b7e7444234d7de67ea26988f6181df;
optional(uint) pubkey;
pubkey.set(0x95c06aa743d1f9000dd64b75498f106af4b7e7444234d7de67ea26988f6181df);
address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123);
m_cell = tvm.buildExtMsg({dest: addr, time: 0x1771c58ef9a, expire: 0x600741e4, call: {Foo.bar, 123, 45}, pubkey: pubkey, sign: true});
m_cell = tvm.buildExtMsg({abiVer: 1, callbackId: 0, onErrorId: 0, dest: addr, time: 0x1771c58ef9a, expire: 0x600741e4, call: {Foo.bar, 111, 88}, pubkey: pubkey, sign: true});
}
}
Expand Down Expand Up @@ -2556,10 +2604,9 @@ require(math.muldivc(3, 7, 2) == 11);
##### math.muldivmod()

```TVMSolidity
math.muldivmod(uint a, uint b, uint c) returns (uint /*result*/, uint /*remainder*/);
math.muldivmod(T a, T b, T c) returns (T /*result*/, T /*remainder*/);
```

Executes TVM instruction "MULDIVMOD" ([TVM][1] - A.5.2. - A98C).
This instruction multiplies first two arguments, divides the result
by third argument and returns the result and the remainder.
Intermediate result is stored in the 514 bit buffer, and the final result
Expand All @@ -2578,6 +2625,26 @@ int g = 2;
(int h, int p) = math.muldivmod(e, f, g);
```

##### math.divmod()

```TVMSolidity
math.divmod(T a, T b) returns (T /*result*/, T /*remainder*/);
```

This instruction divides the first number by the second one and returns the
result and the remainder. Result is rounded to the floor.

Example:

```TVMSolidity
uint a = 3;
uint b = 2;
(uint d, uint r) = math.divmod(a, b);
int e = -1;
int f = 3;
(int h, int p) = math.divmod(e, f);
```

##### **tx** namespace

##### tx.timestamp
Expand Down Expand Up @@ -2718,6 +2785,7 @@ Solidity runtime error codes:
* 63 - See [\<optional(Type)\>.get()](#optionaltypeget).
* 64 - `tvm.buildExtMSg()` call with wrong parameters. See [tvm.buildExtMsg()](#tvmbuildextmsg).
* 65 - Calling of unassigned variable of function type. See [Function type](#function-type).
* 66 - Converting an integer to a string with width less than number length. See [format()](#format).

[1]: https://ton.org/tvm.pdf "TVM"
[2]: https://ton.org/tblkch.pdf "TBLKCH"
Expand Down
6 changes: 6 additions & 0 deletions Changelog_TON.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### 0.36 (2021-02-04)

Assorted features requested by DeBot support:
* `format` function now can create long strings (which does not fit one cell), can take string arguments and format integers width and fill settings.
* Some additional parameters were added for `tvm.buildExtMsg()` and `extMsg` call to support DeBot external function calls and deploy.

### 0.35 (2021-02-01)

Compiler Features:
Expand Down
2 changes: 1 addition & 1 deletion compiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy()

# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.35.0")
set(PROJECT_VERSION "0.36.0")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)

include(TestBigEndian)
Expand Down
119 changes: 89 additions & 30 deletions compiler/libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2574,6 +2574,14 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes.push_back(result);
break;
}
case FunctionType::Kind::MathDivMod:
{
checkArgNumAndIsInteger(arguments, 2, std::equal_to<>(), "This function takes two arguments.");
TypePointer result = getCommonType(arguments);
returnTypes.push_back(result);
returnTypes.push_back(result);
break;
}
case FunctionType::Kind::MathMulDiv:
case FunctionType::Kind::MathMulDivMod:
{
Expand Down Expand Up @@ -2675,6 +2683,34 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes.push_back(TypeProvider::optional(TypeProvider::tuple(members)));
break;
}
case FunctionType::Kind::Format: {
auto cat = arguments[0]->annotation().type->category();
if (cat != Type::Category::StringLiteral) {
m_errorReporter.fatalTypeError(
arguments[0]->location(),
string("First parameter should be a literal string")
);
}
auto lit = dynamic_cast<const Literal *>(arguments[0].get());
std::string format = lit->value();
size_t placeholdersCnt = 0;
for(size_t i = 0; i < format.size() - 1; i++) {
if (format[i] == '{') {
auto c = format[i+1];
if (c == '}' || c == ':')
placeholdersCnt++;
}
}
if (arguments.size() != placeholdersCnt + 1) {
m_errorReporter.fatalTypeError(
_functionCall.location(),
string("Number of arguments is not equal to the number of placeholders!")
);
}
typeCheckFunctionCall(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes();
break;
}
case FunctionType::Kind::TVMBuildExtMsg: {
vector<ASTPointer<ASTString>> const &argumentNames = _functionCall.names();
bool hasNames = !argumentNames.empty();
Expand Down Expand Up @@ -2721,38 +2757,31 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
}
}
int timeIndex = findName("time");
if (timeIndex != -1){
Type const* mt = arguments[timeIndex]->annotation().type->mobileType();
auto isInt = dynamic_cast<IntegerType const*>(mt);
if (isInt == nullptr || isInt->isSigned()) {
m_errorReporter.fatalTypeError(
arguments[timeIndex]->location(),
"\"time\" parameter must have an unsigned integer type."
);
}
if (isInt->numBits() > 64) {
m_errorReporter.fatalTypeError(
arguments[timeIndex]->location(),
"\"time\" parameter must fit in uin64 type."
);
}
}
int expireIndex = findName("expire");
if (expireIndex != -1){
Type const* mt = arguments[expireIndex]->annotation().type->mobileType();
auto isInt = dynamic_cast<IntegerType const*>(mt);
if (isInt == nullptr || isInt->isSigned()) {
std::vector<std::string> intParams= {"time", "expire", "callbackId", "abiVer", "onErrorId"};
std::vector<unsigned>intBits = {64, 32, 32, 8, 32};
std::vector<bool> isMandatory = {false, false, true, true, true};
for (size_t i = 0; i < intParams.size(); i++) {
int nameIndex = findName(intParams[i]);
if (nameIndex != -1){
Type const* mt = arguments[nameIndex]->annotation().type->mobileType();
auto isInt = dynamic_cast<IntegerType const*>(mt);
if (isInt == nullptr || isInt->isSigned()) {
m_errorReporter.fatalTypeError(
arguments[nameIndex]->location(),
string("\"") + intParams[i] + "\" parameter must have an unsigned integer type."
);
}
if (isInt->numBits() > intBits[i])
m_errorReporter.fatalTypeError(
arguments[nameIndex]->location(),
string("\"") + intParams[i] + "\" parameter must fit in uint" + to_string(intBits[i]) + " type."
);
} else if (isMandatory[i]) {
m_errorReporter.fatalTypeError(
arguments[expireIndex]->location(),
"\"expire\" parameter must have an unsigned integer type."
_functionCall.location(),
string("\"") + intParams[i] + "\" parameter must be set."
);
}
if (isInt->numBits() > 32)
m_errorReporter.fatalTypeError(
arguments[expireIndex]->location(),
"\"expire\" parameter must fit in uin32 type."
);
}

int KeyIndex = findName("pubkey");
Expand All @@ -2775,6 +2804,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
}
}
int stateIndex = findName("statInit");
if (stateIndex != -1){
auto cat = arguments[stateIndex]->annotation().type->category();
if (cat !=Type::Category::TvmCell) {
m_errorReporter.fatalTypeError(
arguments[stateIndex]->location(),
"\"stateInit\" parameter must have a TvmCell type."
);
}
}
int SignIndex = findName("sign");
if (SignIndex != -1){
auto ann = arguments[SignIndex]->annotation();
Expand Down Expand Up @@ -2988,6 +3027,8 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
bool setExtMsg = false;
bool setExpire = false;
bool setTime = false;
bool setAbi = false;
bool setOnError = false;

FunctionType::Kind kind = expressionFunctionType->kind();
if (
Expand Down Expand Up @@ -3052,6 +3093,18 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
} else if (name == "pubkey") {
expectType(*_functionCallOptions.options()[i], *TypeProvider::optional(TypeProvider::uint256()));
setCheckOption(setPubkey, "pubkey", false);
} else if (name == "abiVer") {
expectType(*_functionCallOptions.options()[i], *TypeProvider::optional(TypeProvider::uint(8)));
setCheckOption(setAbi, "abiVer", false);
} else if (name == "stateInit") {
expectType(*_functionCallOptions.options()[i], *TypeProvider::optional(TypeProvider::tvmcell()));
setCheckOption(setStateInit, "stateInit", false);
} else if (name == "callbackId") {
expectType(*_functionCallOptions.options()[i], *TypeProvider::optional(TypeProvider::uint(32)));
setCheckOption(setCallback, "callbackId", false);
} else if (name == "onErrorId") {
expectType(*_functionCallOptions.options()[i], *TypeProvider::optional(TypeProvider::uint(32)));
setCheckOption(setOnError, "onErrorId", false);
} else if (name == "expire") {
expectType(*_functionCallOptions.options()[i], *TypeProvider::optional(TypeProvider::uint(32)));
setCheckOption(setExpire, "expire", false);
Expand All @@ -3063,7 +3116,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
_functionCallOptions.location(),
"Unknown external call option \"" +
name +
R"(". Valid options are "extMsg", "sign", "pubkey", "expire" and "time".)"
R"(". Valid options are "extMsg", "abiVer", "callbackId", "onErrorId", "sign", "pubkey", "expire" and "time".)"
);
}
} else {
Expand Down Expand Up @@ -3230,6 +3283,12 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
}
}

if (isExternalInboundMessage && (!setCallback || !setAbi || !setOnError)) {
m_errorReporter.typeError(
_functionCallOptions.location(),
R"("callbackId", "onErrorId" and "abiVer" options must be set.)"
);
}
if (!isExternalInboundMessage && !setCallback && !isNewExpression && !expressionFunctionType->returnParameterTypes().empty()) {
m_errorReporter.typeError(
_functionCallOptions.location(),
Expand Down
3 changes: 2 additions & 1 deletion compiler/libsolidity/analysis/ViewPureChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::Block, "timestamp"},
{MagicType::Kind::Math, "abs"},
{MagicType::Kind::Math, "divc"},
{MagicType::Kind::Math, "divmod"},
{MagicType::Kind::Math, "divr"},
{MagicType::Kind::Math, "max"},
{MagicType::Kind::Math, "min"},
Expand Down Expand Up @@ -388,4 +389,4 @@ bool ViewPureChecker::isStateVariable(Expression const& expression) const {
return varDecl != nullptr && varDecl->isStateVariable();
}
return false;
}
}
Loading

0 comments on commit 5914224

Please sign in to comment.