Taster is the domain-specific language (DSL) for ChainRunner.
Translating the human language (or intention) to computers require complex techniques in programming, especially in blockchain and DeFi. Users who are looking to “swap ETH to USDT” or “short ETH with 2x leverage” would need to go through multiple DeFi platforms, or rather, million lines of coding.
To solve these problems, Taster was developed. Taster helps ChainRunner interpret our language and execute blockchain-based tasks. This type of high-level programming language supporting a particular set of tasks in a specific domain is called a domain-specific language (DSL). DSL allows for increased connectivity among services and is easily comprehensible for anyone who desires to use it.
Taster automatically transpiles needed features into Solidity-based smart contracts, allowing developers to flexibly integrate DeFi protocols, such as Uniswap or Compound. If you are familiar with Python or Javascript, refer to the Taster procedure reference documentation to start using Taster.
Overview
Taster includes four different components: value, control statement (if, for), command, and procedure (function)
Lines of Taster can be used on Gourmet’s REPL driver or on separate files (scenarios)
Control statement and few syntaxes only run when written as scenarios
List of REPL help commands
Value
Use let to save value
Use : to specify type
If value is not needed, use _ to bind
let i = 1;
let i = 1:int256;
let s = "string";
let a = "0x32323241828761" : address;
let by = "101010100101" : bytes;
let by2 = "101" : bytes4;
let b = true;
let _ = fnWithoutReturnValue ();
This shows a simplified version of the abbreviated type
GOURMET> let a = 1 usdt;
GOURMET> a.getType ()
currency
GOURMET> let b = ""
GOURMET> b.getType ()
string
GOURMET> let c = true
GOURMET> c.getType ()
bool
GOURMET> let d = 1
GOURMET> d.getType ()
number
GOURMET>
Procedure (function)
Taster supports function-like procedures like all general languages
proc testProcedure (a, b) {
let c = a;
let d = b;
let e = c + d;
proc isOdd (a) {
if (a % 2 != 0) {
return true;
} else {
return false;
}
}
let returnValue = isOdd (e);
return returnValue;
}
dry
Use dry to call functions or procedures of deployments. This will not affect the blockchains itself.
Ganache must be installed and both public and private keys must be set to env.account
env.blockchain is the Ganache blockchain of the dry call function or procedure
proc mySwap() {
let receivedAmount = swap(...);
return receivedAmount;
}
let receivedTokens = dry mySwap();
print("If you swap now, you can get as much as " + receivedTokens.toString());
dry someBuiltIn();
dry someDeployment.someFunc();
Type
Supports the following types: bool, string, address, bytes, number, array, tuple, solution, deployment, ganache
Bool
Supports ture and false
GOURMET> let isTrue = true;
GOURMET> let isFalse = false;
String
Supports string
GOURMET> let str = "hello";
GOURMET> let str2 = str + " " + "world";
Address
Supports address types
Used the same as String
GOURMET> let usdtAddr = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
Bytes
Supports Bytes types
Used the same as String
GOURMET> let b = "0x123123123"
Number
Supports number types
Define the types int[8-256], uint[8-256] explicitly
If the type is not specified, the type is literal
GOURMET> let i = 1;
GOURMET> let i = 1:uint8;
GOURMET> let i = 1:int256;
Supports int types and number types such as Float and Currencies
Float types will return up to 6 decimal places only, but are fully stored up to 18 decimal places
Currency types represent the amount of coins or tokens on the blockchain networks. The decimal palces for each currency type displayed are the same as the decimal places for the underlying coin or token.
(e.g. USDT(Tether) on Ethereum has 6 decimal places)
You must use unit types with network information (e.g., usdt_eth) for same tokens that have defferent decimals per each blockchain.
GOURMET> 1 usdt
Error : Node is empty.
GOURMET> 1 usdt_eth
1.000000 USDT
GOURMET> 1.000001 usdt_eth
1.000001 USDT
GOURMET> 1.0000011 usdt_eth
Error : The number below the decimal point is too large. (1, 11)~(1, 18)
GOURMET> env.blockchain.set ("http://localhost:8545");
Update Blockchain Node: http://localhost:8545 (Ethereum mainnet)
GOURMET> 1 usdt
1.000000 USDT
Type of UINT
BNB | BFC | BIFI | DAI | ETH | LINK | USDT6 | USDC6 | USDT18 | USDC18 | USDT | USDC | WBTC | BTCB
More will be added to the above list
GOURMET> 1.000001 usdt_eth
1.000001 USDT
GOURMET> 1.0000011 usdt_eth
Error : The number below the decimal point is too large. (1, 11)~(1, 18)
GOURMET>GOURMET> 1 bnb
1.000000 BNB
GOURMET> 1 BNB
1.000000 BNB
GOURMET> 1bnb
1.000000 BNB
GOURMET> 1.123 bnb
1.123000 BNB
GOURMET> 0.0001 bnb
0.000100 BNB
GOURMET>
Array
Declaration or indexing element
GOURMET> let a = [1, 2];
GOURMET> let b = a[0];
GOURMET> let c = a[1];
Chainging the values of an array is not possible
GOURMET> let a = [1, 2];~~let a[0] = 0 // 지원 안함
method
length : returns the length of an array
Tuple
Declaration or indexing elements
GOURMET> let t = (1, "a")
GOURMET> let (t1, t2) = t
GOURMET> let (_, t3) = t
Solution
Created through gen cmd only
GOURMET> let bifiBoostSolution = gen("sample/contracts/BifiBoost.sol", "0.6.12");
GOURMET> let bifiLauncherSolution = gen("sample/contracts/BifiLauncher.sol", "0.6.12");
In the example above, the estimated gas fee to deploy HelloWorld contract is 249256. Multiply the value with the default gas price to compute the amount of gas used.
If the gasPrice is 20000000000 (e.g. the default gasprice of ganache), 249256 * 20000000000 = 4985120000000000
Deployment
Direct declaration is not possible, and can be created through deploy or reg cmd.
GOURMET> let bifiBoostSolution = gen("sample/contracts/BifiBoost.sol", "0.6.12");
GOURMET> bifiBoostSolution.compile ();
GOURMET> let bifiBoost = bifiBoostSolution.BifiBoost(); // Deployment created by deploying the context.
'BifiBoost' was deployed.
'BifiBoost' deploy fee: 43837920000000000.
GOURMET> let bi = reg ("0x602C71e4DAC47a042Ee7f46E0aee17F94A3bA0B6", "/Prelude/ABI/ERC20.ABI.json"); // reg cmd을 통해 생성한 Depolyment
Method
Deployment types support the following methods:
[Deployment].getAddress ()
Returns the deployed contract address
GOURMET> let router = uniswapV2.router02.getDeployment()
GOURMET> router.getAddress()
"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
Calls the specified event from the deployed contract within the specified range of the block numbers
Returns the event deployed between the first and last block
the 0 on the first and last represents the fallowing:
the 0 on the first block represents the deployed block
the 0 on the last block represents the last block number of the using blockchain
GOURMET> d.idFunction (1000)
'idFunction(uint256)' send transaction fee : 451100000000000.
"0xaa16bcf74139d108c7c78964fb56ca06593bba521bd50ce53e805f8188d33b2d"
GOURMET> d.getEventByRange(IdEvent, 0, 0)
Block Number : 12552789
Event Name : IdEvent
Transaction Hash : 0xaa16bcf74139d108c7c78964fb56ca06593bba521bd50ce53e805f8188d33b2d
Event Value : 1000unit
GOURMET> d.getEventByRange(IdEvent, 0, 12552789)
Block Number : 12552789
Event Name : IdEvent
Transaction Hash : 0xaa16bcf74139d108c7c78964fb56ca06593bba521bd50ce53e805f8188d33b2d
Event Value : 1000unit
GOURMET> d.getEventByRange(IdEvent, 12552789, 12552789)
Block Number : 12552789
Event Name : IdEvent
Transaction Hash : 0xaa16bcf74139d108c7c78964fb56ca06593bba521bd50ce53e805f8188d33b2d
Event Value : 1000unit
GOURMET> d.getEventByRange(IdEvent, 12552789, 0)
Block Number : 12552789
Event Name : IdEvent
Transaction Hash : 0xaa16bcf74139d108c7c78964fb56ca06593bba521bd50ce53e805f8188d33b2d
Event Value : 1000unit
[Deployment].[contract function identifier].call ()
[Deployment].[contract function identifier].send ()
Calls the contract function. Receives the function name (not string) and last of factors. call returns the reuturn value of the function and send returns the hash of the transaction.
let bal_tx = ctr_yeh.balanceOf.call (sender);
let bal_rx = ctr_yeh.balanceOf.call (receiver);
let txhash = ctr_yeh.transfer.send (receiver, 33);
let balanceOfSomeone = ctr_bfc.balanceOf.call { Value = 10; Gas = 10 } ("": address);
[Deployment].[contract function identifier].gasEstimate ()
Computes the estimated gas fee when sending the contract function
You can find the actual amount of gas used by multiplying the returned value and gasPrice
Numbers, strings, and logical values are used for comparison operators
Comparison operators: ==, !=, <, >
Only logical values are used for logical operators
Logical operators: ||, &&, !(not)
Bit operator (shift, |, &, etc.) are not supported
+ operators are used to connect strings
GOURMET> print("hello, " + "gourmet!")
"hello, gourmet!"
GOURMET> let result1 = true;
GOURMET> let result2 = false;
GOURMET> let result3 = (result1 || result2) && true;
GOURMET> assert ((result1 || result2) && true);
Assert : True
GOURMET> let a = 10 + 20 * 0;
GOURMET> let b = (10 + 20) * 0;
GOURMET> let c = (10 + 20) / 3;
GOURMET> let d = (10 + 20) % 7;
GOURMET> let e = a == b;
GOURMET> let f = (a == b) && (10 == 10);
GOURMET> assert (a == b);
Assert : False
GOURMET> assert ((a == b) && (10 == 10));
Assert : False
GOURMET> 2 ** 101024
GOURMET> -1
-1
GOURMET> !true
False
GOURMET> let a = "a";
GOURMET> let b = "b";
GOURMET> let c = "a";
GOURMET> assert (a == b);
Assert : False
GOURMET> assert (a == c);
Assert : True
GOURMET> assert (b == "b");
Assert : True
* Assert does not support arithmetic operations.
ex) assert (1 + 3) // not supported
Control Statement
if
Supports branching statement
Can access the variable of the parent branch
A new procedure cannot be declared in an if statement
if statement can be without else
if ([condition]) {[cmd list]} else {[cmd list]}
if ([condition]) {[cmd list]}
if (true) {
let asd = 12313;
EEE ()
if (true) {
AAA.BBB.CCC.EEE ()
print("print asd");
print(asd);
if (true) {
AAA.BBB.CCC.EEE ()
print("print asd");
print(asd);
} else {}
} else {}
} else {}
if (num == 1) { return 1;}
for
Supports looping statement
Can access the variable of the parent (does not pass the procedure)
A new procedure cannot be declared inside the if statement.
Support infinite loops
In the case of infinite loops, exit the loop with return statement
condition :
([var] in [array])
([var] in [number] .. [number])
If there is no condition, it is an infinite loop
proc test () {
let a = 1;
let b = 2;
let arr = [a,b,3,4];
for (a in arr) {
for (b in arr) {
if (a == 2 && b == 4) {
return a + b;
} else {}
}
}
return 0;
}
proc test2 (start, finish) {
let arr = [1,2,3,4];
for (i in start .. finish) {
for (b in arr) {
if (i == 2 && b == 4) {
return i + b;
} else {}
}
}
return 0;
}
proc blockMiningChecker (before) {
for {
if (getBlockNumber () > before) {
print ("A new block has been mined.");
// do something
return 0; // exit loop.
} else {}
}
}
Command
Various commands are supported on Taster
Use the help command to see the command list
help
Returns the command list
Find how to use each command using help [command]
GOURMET> help
[*] Current Gourmet commands (type 'help <command>' for more info):
- aaveV2.flashloan.execute:
- abiEncode: ABI encode values.
- abiEncodeWithSignature: ABI encode with function signature.
- addBlocks: Add dummy blocks.
- assert: Print an assert result
- bifi.eth.getUserAmount:
- bifi.token.getUserAmount:
- clear.all: Clear all context.
- clear.procedures: Procedure list initialize
- clear.variables: Clear all variable.
- compound:
- compound.cEther.getAccountSnapshot:
- compound.cToken.getAccountSnapshot:
- dInfo: Show all deployment information.
- deploymentInfo: Show all deployment information.
- dinfo: Show all deployment information.
- env: Show the environment information.
- env.account.get: Get user account
- env.account.set: Set Account.
- env.activeWallet.get: Get active smart wallet
- env.activeWallet.set: Activate the smart wallet.
- env.blockchain.get: Get current Blockchain
- env.blockchain.set: Set Blockchain Node.
- env.caller.set: Register Caller contract.
- env.defaultGas.set: Set Default Gas.
- env.defaultGasPrice.set: Set Default GasPrice.
- env.defaultValue.set: Set Default Value.
- env.pragma.set: Set Pragma.
- env.recipe.set: Set recipe server.
- env.rpcTimeout.set: Set RPC connection timeout.
- envInfo: Show the environment information.
- erc20.create:
- evalStatus: Print the evaluation status (e.g., binded variables, ...)
- gInfo: Show ganache process information.
- ganacheInfo: Show ganache process information.
- gen: Generate a solution
- genSolution: Generate a solution
- genSolutionTests:
- getAccount: Get user account
- getActiveWallet: Get active smart wallet
- getBalance: Get the balance of an address.
- getBlockNumber: Returns the current block number.
- getBlockchain: Get current Blockchain
- ginfo: Show ganache process information.
- help: Show the usage.
- insert: Insert procedure
- let: Declare a variable.
- pInfo: Show procedure information.
- pinfo: Show procedure information.
- print: Print an expression.
- procedureInfo: Show procedure information.
- reg: Register a contract that has already been deployed.
- regContract: Register a contract that has already been deployed.
- run: Evaluate and print the result of Scenario
- runGanache: Runs ganache-cli process
- runScenario: Evaluate and print the result of Scenario
- sInfo: Show all solutions information.
- setAccount: Set Account.
- setActiveWallet: Activate the smart wallet.
- setBlockchain: Set Blockchain Node.
- setCaller: Register Caller contract.
- setDefaultGas: Set Default Gas.
- setDefaultGasPrice: Set Default GasPrice.
- setDefaultValue: Set Default Value.
- setPragma: Set Pragma.
- setRPCTImeout: Set RPC connection timeout.
- setRecipe: Set recipe server.
- sinfo: Show all solutions information.
- sleep: Suspends the current thread for the specified amount of time.
- smartWallet.auth.add:
- smartWallet.getCallerAddress:
- solutionInfo: Show all solutions information.
- status: Print the evaluation status (e.g., binded variables, ...)
- stopGanache: Stop ganache-cli process
- test.uni:
- test.uni.addLiquidity:
- test.uni.removeLiquidity:
- test.uni.swapExactIn:
- test.uni.swapExactOut:
- this.getAddress(): Get a deployed address when generated
- transfer: Transfer the specific amount of Ether to the address.
- uniswapV2.factory:
- uniswapV2.router02:
clear
Reset the environmental variables or let binded values on Gourmet
Can be used only when using REPL for Gourmet
Autometically loads the procedure defined in Prelude after it is cleared
GOURMET> clearAll context has been cleared.
env
Return various environment values
GOURMET> env
[*] Environment information:
- Recipe Address : http://localhost:9001
- Blockchain Node : http://localhost:8545 (UnknownChain)
- Account ID : 0x3B35A606CA0f807aCB407924E1062dbC8432BB84
- Account Key : key is empty
- Default GasPrice : default gas price
- Default Gas : 6000000 wei
- Default Value : 0 wei
- Experimental Pragma list : []
- RPC connection timeout : 0 Seconds
- Log file : gourmet.log
- Active smart wallet : No active wallet.
- Caller Address : No Caller registered.
Returns or sets each of the environment values
env.recipe.set
Set the recipe server
GOURMET> env.recipe.set
Usage:
env.recipe.set (["Recipe Server URL"]);
setRecipe (["Recipe Server URL"]);
GOURMET> env.recipe.set ("http://localhost:9001");
Update Recipe Server URL: http://localhost:9001
unit
env.account.set
Sets the account
Can set only the publicKey of the account
GOURMET> let publicKey = "0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706";
GOURMET> let privateKey = "0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed";
GOURMET> let acc = (publicKey, privateKey);
GOURMET> env.account.set (acc);
Update Account:
Account ID : 0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706
Account Key: 0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed
unit
GOURMET> env.account.set (publicKey, privateKey);
Update Account:
Account ID : 0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706
Account Key: 0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed
unit
GOURMET> env.account.set (publicKey);
Update Account:
Account ID : 0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706
unit
GOURMET>
env.account.get
Retrieves the account information. Return type is tuple.
GOURMET> let publicKey = "0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706";
GOURMET> let privateKey = "0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed";
GOURMET> let acc = (publicKey, privateKey);
GOURMET> env.account.set (acc);
Update Account:
Account ID : 0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706
Account Key: 0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed
unit
GOURMET> env.account.get ();
("0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706", "0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed")
GOURMET> env.account.set (publicKey, privateKey);
Update Account:
Account ID : 0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706
Account Key: 0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed
unit
GOURMET> env.account.get ();
("0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706", "0x9d7c3ede1667fd74e7308b1304b96eb1ba4f3d7bcb08d3c6988ed1aa5c3e5bed")
GOURMET> env.account.set (publicKey);
Update Account:
Account ID : 0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706
unit
GOURMET> env.account.get ();
("0x8BAA3237cc980C0436Cd4d494Dd0Ac4a109F7706", "")
GOURMET>
env.blockchain.set
Sets the blockchain node
Enters the address as string format
Can use the binded label withrunGanache cmd
GOURMET> env.blockchain.set ("http://localhost:8545");
Update Blockchain Node: http://localhost:8545
unit
GOURMET> let g = runGanache()
GOURMET> env.blockchain.set (g);
Update Blockchain Node: http://localhost:8545
unit
GOURMET>
env.blockchain.get
Retrieves the blockchain node. Return type is tuple
GOURMET> let str = "hello";
GOURMET> let str2 = str + " " + "world";
GOURMET> print (str2);
"hello world"
runGanache
runGanache runs ganache-cli and creates Ganache object with Ganache instance
Enter the options that ganache-cli supports when running runGanache
The options that can be recived on Taster are as follows: Fork, Mnemonic, Hardfork, Port
You may set the field for options
If you don't set any options, the option runs default
GOURMET> runGanache (mnemonic: "brownie", hardfork: "istanbul", port: 8545)
GOURMET> let g = runGanache (mnemonic: "brownie", hardfork: "istanbul", port: 8545)
GOURMET> ganacheInfo
[*] Ganache process information
| Process port : 8545
| Fork provider :
| Hardfork : istanbul
| Mnemonic : brownie
| Available Accounts :
(0) "0x66ab6d9362d4f35596279692f0251db635165871"
(1) "0x33a4622b82d4c04a53e170c638b944ce27cffce3"
(2) "0x0063046686e46dc6f15918b61ae2b121458534a5"
(3) "0x21b42413ba931038f35e7a5224fadb065d297ba3"
(4) "0x46c0a5326e643e4f71d3149d50b48216e174ae84"
(5) "0x807c47a89f720fe4ee9b8343c286fc886f43191b"
(6) "0x844ec86426f076647a5362706a04570a5965473b"
(7) "0x23bb2bb6c340d4c91caa478edf6593fc5c4a6d4b"
(8) "0xa868bc7c1af08b8831795fac946025557369f69c"
(9) "0x1cee82eed89bd5be5bf2507a92a755dcf1d8e8dc"
genSolution (gen)
Create Solution object with the source code or procedure
When created with source code:
GOURMET> let rol = gen ("..\rolSample\test.rol", "0.6.12")
GOURMET> let sol = gen ("sample\sol\test.sol", "0.6.12")
GOURMET> sInfo
[*] Solution information
| Binded variable id : rol
| Solution name : erc20TestSolution
| Solidity version : 0.6.12
| Contract information
| Binded variable id : sol
| Solution name : test
| Solidity version : 0.6.12
| Contract information
GOURMET>
You may use with the following languages and version :
Solidity
ROL
"0.5.17"
"0.6.5"
"0.6.12"
"0.8.0"
To use another version of solidity, you have to do two things: 1) you must give the specific solidity version (as the second parameter) to Chainrunner by using gen cmd; and 2) the specific version of solidity compiler (solc) is placed to “/Gourmet/[TargetOS]/”.
For example, set solc0.7.0 file to the directory of the compiler and run gen ("someSolidity.sol", "0.7.0")
When created with procedure:
proc addSub(x, y) {
let add = x + y;
let sub = x - y;
return (add, sub);
}
let s = gen( // 변환하는 프로시져의 이름 addSub, // 타입을 명시할 프로시져 이름, 매개변수 타입, 결과 타입 [("addSub", ["uint256", "uint256"], ["uint256", "uint256"])]);
s.compile(optimize: true);
let d = s.StubContract();
d.stubFunction(10, 20);
The solution name created with procedure is StubContract and the function is stubFunction
The parameters of the procedure and the return value type must be specified. If there is no paramter or return value, it can be omitted.
proc dumb() {}
let s = gen(dumb);
The procedures that require off-chain processes, such as env-based commands, reg, or gen, cannot be used.
The procedure can call function as env.account using delegateCall.
// 아래 호출은 생성되는 컨트렉트가 아닌, env.account의 명의로 실행됩니다.
let ok = weth.transfer.delegateCall(to);
if (!ok) { ... }
In procedures that creates the solution, you can avoid type ambiguity of variables by specifying type information.
"hello": has type ambiguity
10: has type ambiguity
// `0`은 부호와 크기가 다양할 수 있는 수를 표현하기 때문에, 명시 필요
let sum = 0:int;
// 한 번 타입을 안 이후에는, 명시할 필요 없음
let sum = sum + 1;
In procedures that creates the solution, deployment calls can only be made in variable delcaration. If multiple values are returned, they must be closed immediately after it is returned.
let x = d.returnTwoValues(); // invalid
let (x, y) = d.returnTwoValues(); // good
May deploy the compiled contract
Cannot use the deployed deployment. Declaration for variable is made, but the feature for re-using the variable is currently not implemented.
GOURMET> let recipient = "0x379c0ac6c6091195921b410ed37519076ec68340";
GOURMET> let amount = 1000000000000000000;
GOURMET> transfer (recipient, amount);
assert
Provides the assert feature for executing each cmd
Ends the scenario if it fails to assert when executing Gourmet with CliDriver
Returns the current location of the procedure when the procedure is genSolution()and deployed
Returns "0x0":address when running on Gourmet
proc p() {
let a = this.getAddress()
}
p(); // a = "0x0":address
let s = gen(p, [], ["address"]);
s.compile();
let d = s.StubContract();
d.stubFunction(); // a = d.getAddress()
addBlocks
Increases the block number
Creates a block with a dummy transaction
Runs only in private blockchains such as ganache
GOURMET> addBlocks (2);
2 block(s) was added.
unit
GOURMET>