Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
Signed-off-by: hunknownz <[email protected]>
  • Loading branch information
hunknownz committed Nov 10, 2024
0 parents commit 1a040ff
Show file tree
Hide file tree
Showing 29 changed files with 6,249 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.html linguist-vendored
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:

jobs:
test:
strategy:
fail-fast: false
matrix:
node-version: [22]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Install Soufflé on Ubuntu
if: matrix.os == 'ubuntu-latest'
run: |
sudo wget https://souffle-lang.github.io/ppa/souffle-key.public -O /usr/share/keyrings/souffle-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/souffle-archive-keyring.gpg] https://souffle-lang.github.io/ppa/ubuntu/ stable main" | sudo tee /etc/apt/sources.list.d/souffle.list
sudo apt update
sudo apt install souffle
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: yarn install

- name: Build
run: yarn build

- name: Run Misti
run: yarn misti --min-severity medium ./tact.config.json

- name: Run tests
run: yarn test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Tact template project

This template comes pre-configured to kickstart your new Tact project. It includes the Tact compiler, TypeScript, Jest integrated with [tact-emulator](https://github.com/tact-lang/tact-emulator), and a sample demonstrating how to run tests.

```shell
yarn test # To test contract
yarn build # To build contract
yarn lint # To find code issues in contract
yarn deploy # To deploy contract
```

## Deployment

To deploy a contract, follow these steps:

1. Define the [`contract.tact`](./sources/contract.tact) file that will be used as entry point of your contract.
2. Customize the [`contract.deploy.ts`](./sources/contract.deploy.ts) file based on your `contract.tact` to generate a deployment link. It is crucial to ensure the proper invocation of the `init()` function within the contract.

If you rename `contract.tact`, make sure to update [`tact.config.json`](./tact.config.json) correspondingly. Refer to the [Tact Documentation](https://docs.tact-lang.org/language/guides/config) for detailed information.

## Testing

You can find some examples of contract tests in [`contract.spec.ts`](./sources/contract.spec.ts). For more information about testing, see the [Tact Documentation](https://docs.tact-lang.org/language/guides/debug).

To add new test files to your contracts, you should create `*.spec.ts` files similar to the template's one and they would be automatically included in testing.

## License

MIT
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
snapshotSerializers: ["@tact-lang/ton-jest/serializers"],
globalSetup: './jest.setup.js',
globalTeardown: './jest.teardown.js',
};
1 change: 1 addition & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = async () => { };
3 changes: 3 additions & 0 deletions jest.teardown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const path = require('path');

module.exports = async () => { };
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"private": true,
"scripts": {
"build": "tact --config ./tact.config.json",
"lint": "yarn misti ./tact.config.json",
"test": "jest",
"deploy": "ts-node ./sources/contract.deploy.ts",
"read": "ts-node ./sources/contract.read.ts"
},
"dependencies": {
"@nowarp/misti": "~0.5.0",
"@tact-lang/compiler": "~1.5.0",
"@tact-lang/deployer": "^0.2.0",
"@tact-lang/ton-abi": "^0.0.3",
"@tact-lang/ton-jest": "^0.0.4",
"@ton/core": "~0.56.3",
"@ton/crypto": "^3.2.0",
"@ton/sandbox": "^0.20.0",
"@ton/test-utils": "^0.4.2",
"@ton/ton": "^13.9.0",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.14",
"@types/qs": "^6.9.7",
"base64url": "^3.0.1",
"enquirer": "^2.3.6",
"jest": "^29.3.1",
"open": "^8.4.0",
"prando": "^6.0.1",
"prettier": "^2.5.1",
"qs": "^6.11.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
}
59 changes: 59 additions & 0 deletions sources/Bet.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import "@stdlib/deploy";
import "./goal.tact";
import "./Github.tact";
message LinkWallet {
address: Address;
github: String;
}

contract Bet with Deployable {
owner: Address;
goals: map<Int as uint256, Goal>;
tasks: map<Int as uint256, Task>;
taskIndices: map<Int as uint256, Int as uint256>;
userGoals: map<Address, UserGoals>;
walletToGithub: map<Address, GithubUser>;
githubToWallet: map<Int as uint256, Address>;
userPoints: map<Address, Int as uint256>;
userCompletedTasks: map<Address, CompletedTasks>;
// reward realted
projects: map<Int as uint256, Project>;
projectIds: map<Int as uint256, Int as uint256>;
totalRewards: map<Address, Int as uint256>;
claimedRewards: map<Address, Int as uint256>;
init(owner: Address){
self.owner = owner;
}

fun createGoalSolo(name: String,
description: String,
requiredStake: Int,
taskCount: Int) {
// Check sender
let ctx: Context = context();
require(ctx.sender == self.owner, "Invalid sender");
}

fun createGoalFunc(name: String,
description: String,
requiredStake: Int,
taskCount: Int,
goalType: Int) {
// Check sender
let ctx: Context = context();
require(ctx.sender == self.owner, "Invalid sender");
}

fun linkWallet(address: Address, github: String) {
let walletUser: GithubUser? = self.walletToGithub.get(address);
require(walletUser == null, "Wallet already linked to a Github account");
let githubUser: GithubUser = GithubUser{githubUsername: github};
self.walletToGithub.set(address, githubUser);
let githubHash: Int = sha256(github);
self.githubToWallet.set(githubHash, address);
}

receive(msg: LinkWallet){
self.linkWallet(msg.address, msg.github);
}
}
3 changes: 3 additions & 0 deletions sources/Github.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
struct GithubUser {
githubUsername: String;
}
45 changes: 45 additions & 0 deletions sources/Goal.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
struct Goal {
id: Int as uint256;
name: String;
description: String;
requiredStake: Int as uint256;
creator: Address;
completed: Bool;
participants: map<Int as uint256, Address>; // TODO: change to parent-child
taskCount: Int as uint256;
goalType: Int;
isParticipant: map<Address, Bool>;
isClaimed: map<Address, Bool>;
completedTaskCount: map<Address, Int as uint256>;
rewards: map<Address, Int as uint256>;
}
struct GoalInfo {
id: Int as uint256;
name: String;
description: String;
requiredStake: Int as uint256;
creator: Address;
completed: Bool;
participants: map<Int as uint256, Address>;
goalType: Int;
}
struct Task {
id: String;
name: String;
completed: Bool;
projectId: String;
taskCompleter: Address;
}
struct Project {
id: String;
userPoints: map<Address, Int as uint256>;
participants: map<Int as uint256, Address>;
}
struct UserGoals {
address: Address;
goals: map<Int as uint256, Int as uint256>;
}
struct CompletedTasks {
address: Address;
tasks: map<Int as uint256, Int as uint256>;
}
2 changes: 2 additions & 0 deletions sources/GoalType.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const GoalTypeSolo: Int = 0;
const GoalTypeGambling: Int = 1;
47 changes: 47 additions & 0 deletions sources/contract.deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as fs from "fs";
import * as path from "path";
import { Address, contractAddress } from "@ton/core";
import { Bet } from "./output/Bet";
import { prepareTactDeployment } from "@tact-lang/deployer";

(async () => {
// Parameters
let testnet = true;
let packageName = "Bet.pkg";
let owner = Address.parse("kQBM7QssP28PhrctDOyd47_zpFfDiQvv5V9iXizNopb1d2LB");
let init = await Bet.init(owner);

// Load required data
let address = contractAddress(0, init);
let data = init.data.toBoc();
let pkg = fs.readFileSync(path.resolve(__dirname, "output", packageName));

// Prepareing
console.log("Uploading package...");
let prepare = await prepareTactDeployment({ pkg, data, testnet });

// Deploying
console.log(
"============================================================================================"
);
console.log("Contract Address");
console.log(
"============================================================================================"
);
console.log();
console.log(address.toString({ testOnly: testnet }));
console.log();
console.log(
"============================================================================================"
);
console.log("Please, follow deployment link");
console.log(
"============================================================================================"
);
console.log();
console.log(prepare);
console.log();
console.log(
"============================================================================================"
);
})();
23 changes: 23 additions & 0 deletions sources/contract.read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Address, contractAddress } from "@ton/core";
import { TonClient4 } from "@ton/ton";
import { Bet } from "./output/Bet";

(async () => {
const client = new TonClient4({
endpoint: "https://sandbox-v4.tonhubapi.com", // 🔴 Test-net API endpoint
});

// Parameters
let owner = Address.parse("kQBM7QssP28PhrctDOyd47_zpFfDiQvv5V9iXizNopb1d2LB");
let init = await Bet.init(owner);
let contract_address = contractAddress(0, init);

// Prepareing
console.log("Reading Contract Info...");
console.log(contract_address);

// Input the contract address
let contract = await YouBetContract.fromAddress(contract_address);
let contract_open = await client.open(contract);
console.log("Counter Value: " + (await contract_open.getCounter()));
})();
44 changes: 44 additions & 0 deletions sources/contract.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { toNano } from "@ton/core";
import { Blockchain } from "@ton/sandbox";
import "@ton/test-utils";
import { Bet } from "./output/youbet_Bet";
import { findErrorCodeByMessage } from "./utils/error";

describe("contract", () => {
it("should deploy correctly", async () => {
// Create Sandbox and deploy contract
let system = await Blockchain.create();
let owner = await system.treasury("owner");
let contract = system.openContract(await Bet.fromInit(owner.address));
const deployResult = await contract.send(
owner.getSender(),
{ value: toNano(1) },
{ $$type: "Deploy", queryId: 0n }
);
expect(deployResult.transactions).toHaveTransaction({
from: owner.address,
to: contract.address,
deploy: true,
success: true,
});
});
});

describe("linkWallet", () => {
it("should link correctly", async () => {
// Create Sandbox and deploy contract
let system = await Blockchain.create();
let owner = await system.treasury("owner");
let contract = system.openContract(await Bet.fromInit(owner.address));
const linkResult = await contract.send(
owner.getSender(),
{ value: toNano(1) },
{ $$type: "LinkWallet", address: owner.address, github: "github" }
);
expect(linkResult.transactions).toHaveTransaction({
from: owner.address,
to: contract.address,
success: true,
});
});
});
1 change: 1 addition & 0 deletions sources/output/youbet_Bet.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"Bet","types":[{"name":"StateInit","header":null,"fields":[{"name":"code","type":{"kind":"simple","type":"cell","optional":false}},{"name":"data","type":{"kind":"simple","type":"cell","optional":false}}]},{"name":"StdAddress","header":null,"fields":[{"name":"workchain","type":{"kind":"simple","type":"int","optional":false,"format":8}},{"name":"address","type":{"kind":"simple","type":"uint","optional":false,"format":256}}]},{"name":"VarAddress","header":null,"fields":[{"name":"workchain","type":{"kind":"simple","type":"int","optional":false,"format":32}},{"name":"address","type":{"kind":"simple","type":"slice","optional":false}}]},{"name":"Context","header":null,"fields":[{"name":"bounced","type":{"kind":"simple","type":"bool","optional":false}},{"name":"sender","type":{"kind":"simple","type":"address","optional":false}},{"name":"value","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"raw","type":{"kind":"simple","type":"slice","optional":false}}]},{"name":"SendParameters","header":null,"fields":[{"name":"bounce","type":{"kind":"simple","type":"bool","optional":false}},{"name":"to","type":{"kind":"simple","type":"address","optional":false}},{"name":"value","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"mode","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"body","type":{"kind":"simple","type":"cell","optional":true}},{"name":"code","type":{"kind":"simple","type":"cell","optional":true}},{"name":"data","type":{"kind":"simple","type":"cell","optional":true}}]},{"name":"Deploy","header":2490013878,"fields":[{"name":"queryId","type":{"kind":"simple","type":"uint","optional":false,"format":64}}]},{"name":"DeployOk","header":2952335191,"fields":[{"name":"queryId","type":{"kind":"simple","type":"uint","optional":false,"format":64}}]},{"name":"FactoryDeploy","header":1829761339,"fields":[{"name":"queryId","type":{"kind":"simple","type":"uint","optional":false,"format":64}},{"name":"cashback","type":{"kind":"simple","type":"address","optional":false}}]},{"name":"Goal","header":null,"fields":[{"name":"id","type":{"kind":"simple","type":"uint","optional":false,"format":256}},{"name":"name","type":{"kind":"simple","type":"string","optional":false}},{"name":"description","type":{"kind":"simple","type":"string","optional":false}},{"name":"requiredStake","type":{"kind":"simple","type":"uint","optional":false,"format":256}},{"name":"creator","type":{"kind":"simple","type":"address","optional":false}},{"name":"completed","type":{"kind":"simple","type":"bool","optional":false}},{"name":"participants","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"address"}},{"name":"taskCount","type":{"kind":"simple","type":"uint","optional":false,"format":256}},{"name":"goalType","type":{"kind":"simple","type":"int","optional":false,"format":257}},{"name":"isParticipant","type":{"kind":"dict","key":"address","value":"bool"}},{"name":"isClaimed","type":{"kind":"dict","key":"address","value":"bool"}},{"name":"completedTaskCount","type":{"kind":"dict","key":"address","value":"uint","valueFormat":256}},{"name":"rewards","type":{"kind":"dict","key":"address","value":"uint","valueFormat":256}}]},{"name":"GoalInfo","header":null,"fields":[{"name":"id","type":{"kind":"simple","type":"uint","optional":false,"format":256}},{"name":"name","type":{"kind":"simple","type":"string","optional":false}},{"name":"description","type":{"kind":"simple","type":"string","optional":false}},{"name":"requiredStake","type":{"kind":"simple","type":"uint","optional":false,"format":256}},{"name":"creator","type":{"kind":"simple","type":"address","optional":false}},{"name":"completed","type":{"kind":"simple","type":"bool","optional":false}},{"name":"participants","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"address"}},{"name":"goalType","type":{"kind":"simple","type":"int","optional":false,"format":257}}]},{"name":"Task","header":null,"fields":[{"name":"id","type":{"kind":"simple","type":"string","optional":false}},{"name":"name","type":{"kind":"simple","type":"string","optional":false}},{"name":"completed","type":{"kind":"simple","type":"bool","optional":false}},{"name":"projectId","type":{"kind":"simple","type":"string","optional":false}},{"name":"taskCompleter","type":{"kind":"simple","type":"address","optional":false}}]},{"name":"Project","header":null,"fields":[{"name":"id","type":{"kind":"simple","type":"string","optional":false}},{"name":"userPoints","type":{"kind":"dict","key":"address","value":"uint","valueFormat":256}},{"name":"participants","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"address"}}]},{"name":"UserGoals","header":null,"fields":[{"name":"address","type":{"kind":"simple","type":"address","optional":false}},{"name":"goals","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"uint","valueFormat":256}}]},{"name":"CompletedTasks","header":null,"fields":[{"name":"address","type":{"kind":"simple","type":"address","optional":false}},{"name":"tasks","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"uint","valueFormat":256}}]},{"name":"GithubUser","header":null,"fields":[{"name":"githubUsername","type":{"kind":"simple","type":"string","optional":false}}]},{"name":"LinkWallet","header":1864514037,"fields":[{"name":"address","type":{"kind":"simple","type":"address","optional":false}},{"name":"github","type":{"kind":"simple","type":"string","optional":false}}]},{"name":"Bet$Data","header":null,"fields":[{"name":"owner","type":{"kind":"simple","type":"address","optional":false}},{"name":"goals","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"Goal","valueFormat":"ref"}},{"name":"tasks","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"Task","valueFormat":"ref"}},{"name":"taskIndices","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"uint","valueFormat":256}},{"name":"userGoals","type":{"kind":"dict","key":"address","value":"UserGoals","valueFormat":"ref"}},{"name":"walletToGithub","type":{"kind":"dict","key":"address","value":"GithubUser","valueFormat":"ref"}},{"name":"githubToWallet","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"address"}},{"name":"userPoints","type":{"kind":"dict","key":"address","value":"uint","valueFormat":256}},{"name":"userCompletedTasks","type":{"kind":"dict","key":"address","value":"CompletedTasks","valueFormat":"ref"}},{"name":"projects","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"Project","valueFormat":"ref"}},{"name":"projectIds","type":{"kind":"dict","key":"uint","keyFormat":256,"value":"uint","valueFormat":256}},{"name":"totalRewards","type":{"kind":"dict","key":"address","value":"uint","valueFormat":256}},{"name":"claimedRewards","type":{"kind":"dict","key":"address","value":"uint","valueFormat":256}}]}],"receivers":[{"receiver":"internal","message":{"kind":"typed","type":"LinkWallet"}},{"receiver":"internal","message":{"kind":"typed","type":"Deploy"}}],"getters":[],"errors":{"2":{"message":"Stack underflow"},"3":{"message":"Stack overflow"},"4":{"message":"Integer overflow"},"5":{"message":"Integer out of expected range"},"6":{"message":"Invalid opcode"},"7":{"message":"Type check error"},"8":{"message":"Cell overflow"},"9":{"message":"Cell underflow"},"10":{"message":"Dictionary error"},"11":{"message":"'Unknown' error"},"12":{"message":"Fatal error"},"13":{"message":"Out of gas error"},"14":{"message":"Virtualization error"},"32":{"message":"Action list is invalid"},"33":{"message":"Action list is too long"},"34":{"message":"Action is invalid or not supported"},"35":{"message":"Invalid source address in outbound message"},"36":{"message":"Invalid destination address in outbound message"},"37":{"message":"Not enough TON"},"38":{"message":"Not enough extra-currencies"},"39":{"message":"Outbound message does not fit into a cell after rewriting"},"40":{"message":"Cannot process a message"},"41":{"message":"Library reference is null"},"42":{"message":"Library change action error"},"43":{"message":"Exceeded maximum number of cells in the library or the maximum depth of the Merkle tree"},"50":{"message":"Account state size exceeded limits"},"128":{"message":"Null reference exception"},"129":{"message":"Invalid serialization prefix"},"130":{"message":"Invalid incoming message"},"131":{"message":"Constraints error"},"132":{"message":"Access denied"},"133":{"message":"Contract stopped"},"134":{"message":"Invalid argument"},"135":{"message":"Code of a contract was not found"},"136":{"message":"Invalid address"},"137":{"message":"Masterchain support is not enabled for this contract"},"4429":{"message":"Invalid sender"},"44355":{"message":"Wallet already linked to a Github account"}},"interfaces":["org.ton.introspection.v0","org.ton.abi.ipfs.v0","org.ton.deploy.lazy.v0","org.ton.chain.workchain.v0"]}
Binary file added sources/output/youbet_Bet.code.boc
Binary file not shown.
Loading

0 comments on commit 1a040ff

Please sign in to comment.