diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..63f8189 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "contracts/dns-contract"] + path = contracts/dns-contract + url = https://github.com/ton-blockchain/dns-contract.git diff --git a/contracts/dns-contract b/contracts/dns-contract new file mode 160000 index 0000000..d081310 --- /dev/null +++ b/contracts/dns-contract @@ -0,0 +1 @@ +Subproject commit d08131031fb659d2826cccc417ddd9b98476f814 diff --git a/contracts/main.ts b/contracts/main.ts index 5fafd0b..35c8c10 100644 --- a/contracts/main.ts +++ b/contracts/main.ts @@ -1,32 +1,57 @@ import BN from "bn.js"; import { Cell, beginCell, Address } from "ton"; +import { C7Config, SmartContract } from "ton-contract-executor"; import {encodeOffChainContent, makeSnakeCell} from "./utils"; // encode contract storage according to save_data() contract method -// collection_content:^Cell // nft_item_code:^Cell // uint256 index // MsgAddressInt collection_address // MsgAddressInt owner_address +// collection_content:^Cell // cell domain - e.g contains "alice" (without ending \0) for "alice.ton" domain // cell auction - auction info -// int last_fill_up_time +// int64 last_fill_up_time export function data(params: { ownerAddress: Address; collectionAddress: Address, code: Cell, domain: String }): Cell { return beginCell() - .storeRef(encodeOffChainContent("https://agorata.io/collection.json")) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md - // For code: https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-auction/build.sh - .storeRef(params.code) - .storeUint8(0) - .storeAddress(params.collectionAddress) - .storeAddress(params.ownerAddress) - .storeRef(makeSnakeCell(Buffer.from(params.domain))) - .storeRef(beginCell().endCell()) - .storeUint(0, 64).endCell(); + // For code: https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-auction/build.sh + .storeRef(params.code) + .storeUint(0, 256) + .storeAddress(params.collectionAddress) + .storeAddress(params.ownerAddress) + .storeRef(encodeOffChainContent("https://agorata.io/collection.json")) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md + .storeRef(makeSnakeCell(Buffer.from(params.domain))) + .storeDict(null) + .storeUint(0, 64).endCell(); } +export function auctionWithWinner(winnerAddress: Address) { + return beginCell().storeAddress(winnerAddress).storeCoins(0).storeUint(0, 64) +} + +export function setContractBalance(contract: SmartContract, balance: number) { + contract.setC7Config({balance: balance}); +} + +export function TON(): number { return 1000000000; } + // message encoders for all ops (see contracts/imports/constants.fc for consts) export function transferOwnership(params: { newOwnerAddress: Address }): Cell { - return beginCell().storeUint(0x2da38aaf, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).endCell(); + return beginCell().storeUint(0x5fcc3d14, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).storeAddress(null).storeInt(0, 1).storeCoins(1 * TON()).endCell(); +} + +export function currentState(contract: SmartContract) { + let reader = contract.dataCell.beginParse(); + return { + nft_item_code: reader.readRef(), + index: reader.readUint(256), + collectionAddress: reader.readAddress(), + ownerAddress: reader.readAddress(), + collectionContent: reader.readRef(), + domain: reader.readRef(), + // auction: reader.readCell(), – TODO: still havent's figured out to load auction here + // lastFillUpTime: reader.readInt(64) + } } diff --git a/package-lock.json b/package-lock.json index d97c6f6..f0b25b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "mocha": "^9.1.3", "prando": "^6.0.1", "prettier": "^2.6.2", - "ton": "^12.1.3", + "ton": "9.9.0", "ton-contract-executor": "^0.4.8", "ton-crypto": "^3.1.0", "ts-node": "^10.4.0", @@ -1715,9 +1715,9 @@ } }, "node_modules/ton": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/ton/-/ton-12.1.3.tgz", - "integrity": "sha512-ZkXyr2UkYEQfCX5QKY9xQfzLyM4MDZE+Lz7RICr74wS3fwk5lkJ1/KrQZqb2/DwtoBonEaiNjm6x+r86Q8peFA==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/ton/-/ton-9.9.0.tgz", + "integrity": "sha512-6t+/5b/6DbQH58ywJoc96CN1txcxXjZfYXk/vDeJClqfFR5Z9QikfoPGjLPHTkpLo8jALGPrcKnZDYIwU6biew==", "dev": true, "dependencies": { "axios": "^0.25.0", @@ -1765,36 +1765,6 @@ "ton-compiler": "^0.9.0" } }, - "node_modules/ton-contract-executor/node_modules/ton": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/ton/-/ton-9.9.0.tgz", - "integrity": "sha512-6t+/5b/6DbQH58ywJoc96CN1txcxXjZfYXk/vDeJClqfFR5Z9QikfoPGjLPHTkpLo8jALGPrcKnZDYIwU6biew==", - "dev": true, - "dependencies": { - "axios": "^0.25.0", - "bn.js": "5.2.0", - "dataloader": "^2.0.0", - "ethjs-unit": "0.1.6", - "fp-ts": "^2.11.1", - "io-ts": "^2.2.16", - "io-ts-reporters": "^2.0.0", - "symbol.inspect": "1.0.1", - "teslabot": "^1.3.0", - "ton-crypto": "2.1.0", - "tweetnacl": "1.0.3" - } - }, - "node_modules/ton-contract-executor/node_modules/ton-crypto": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-2.1.0.tgz", - "integrity": "sha512-PZnmCOShfgq9tCRM8E7hG8nCkpkOyZvDLPXmZN92ZEBrfTT0NKKf0imndkxG5DkgWMjc6IKfgpnEaJDH9qN6ZQ==", - "dev": true, - "dependencies": { - "jssha": "3.2.0", - "ton-crypto-primitives": "2.0.0", - "tweetnacl": "1.0.3" - } - }, "node_modules/ton-crypto": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-3.1.0.tgz", @@ -3191,9 +3161,9 @@ } }, "ton": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/ton/-/ton-12.1.3.tgz", - "integrity": "sha512-ZkXyr2UkYEQfCX5QKY9xQfzLyM4MDZE+Lz7RICr74wS3fwk5lkJ1/KrQZqb2/DwtoBonEaiNjm6x+r86Q8peFA==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/ton/-/ton-9.9.0.tgz", + "integrity": "sha512-6t+/5b/6DbQH58ywJoc96CN1txcxXjZfYXk/vDeJClqfFR5Z9QikfoPGjLPHTkpLo8jALGPrcKnZDYIwU6biew==", "dev": true, "requires": { "axios": "^0.25.0", @@ -3249,38 +3219,6 @@ "bn.js": "^5.2.0", "ton": "^9.6.3", "ton-compiler": "^0.9.0" - }, - "dependencies": { - "ton": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/ton/-/ton-9.9.0.tgz", - "integrity": "sha512-6t+/5b/6DbQH58ywJoc96CN1txcxXjZfYXk/vDeJClqfFR5Z9QikfoPGjLPHTkpLo8jALGPrcKnZDYIwU6biew==", - "dev": true, - "requires": { - "axios": "^0.25.0", - "bn.js": "5.2.0", - "dataloader": "^2.0.0", - "ethjs-unit": "0.1.6", - "fp-ts": "^2.11.1", - "io-ts": "^2.2.16", - "io-ts-reporters": "^2.0.0", - "symbol.inspect": "1.0.1", - "teslabot": "^1.3.0", - "ton-crypto": "2.1.0", - "tweetnacl": "1.0.3" - } - }, - "ton-crypto": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ton-crypto/-/ton-crypto-2.1.0.tgz", - "integrity": "sha512-PZnmCOShfgq9tCRM8E7hG8nCkpkOyZvDLPXmZN92ZEBrfTT0NKKf0imndkxG5DkgWMjc6IKfgpnEaJDH9qN6ZQ==", - "dev": true, - "requires": { - "jssha": "3.2.0", - "ton-crypto-primitives": "2.0.0", - "tweetnacl": "1.0.3" - } - } } }, "ton-crypto": { diff --git a/package.json b/package.json index 59f45fc..dc67c33 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "mocha": "^9.1.3", "prando": "^6.0.1", "prettier": "^2.6.2", - "ton": "^12.1.3", + "ton": "9.9.0", "ton-contract-executor": "^0.4.8", "ton-crypto": "^3.1.0", "ts-node": "^10.4.0", diff --git a/test/ownership.spec.ts b/test/ownership.spec.ts index 3a16960..50adafe 100644 --- a/test/ownership.spec.ts +++ b/test/ownership.spec.ts @@ -1,9 +1,9 @@ -import chai, { expect } from "chai"; +import chai, { assert, expect } from "chai"; import chaiBN from "chai-bn"; import BN from "bn.js"; chai.use(chaiBN(BN)); -import { Cell, Slice } from "ton"; +import { Builder, Cell, Slice } from "ton"; import { SmartContract } from "ton-contract-executor"; import * as main from "../contracts/main"; import { internalMessage, randomAddress } from "./helpers"; @@ -12,19 +12,62 @@ import { hex } from "../build/main.compiled.json"; describe("Transfer ownership tests", () => { let contract: SmartContract; + let debug: boolean = false; beforeEach(async () => { contract = await SmartContract.fromCell( - Cell.fromBoc(hex)[0], // code cell from build output + Cell.fromBoc(hex)[0], main.data({ ownerAddress: randomAddress("owner"), code: Cell.fromBoc(hex)[0], collectionAddress: randomAddress("collection"), domain: "alice", - }) + }), + { debug: debug } ); }); + it("transfers ownership to self with no error", async () => { + main.setContractBalance(contract, 10 * main.TON()); + let ownerAddr = randomAddress("owner"); + const sendToSelfMessage = internalMessage({ + from: ownerAddr, + body: main.transferOwnership({ newOwnerAddress: ownerAddr }), + }); + const res = await contract.sendInternalMessage(sendToSelfMessage); + expect(res.type).to.equal("success"); + expect(res.exit_code).to.equal(0); + assert(main.currentState(contract).ownerAddress?.equals(ownerAddr), "owner address changed after transferring to self"); + }); + + it("should allow the owner to transfer ownership", async () => { + main.setContractBalance(contract, 10 * main.TON()); + let ownerAddr = randomAddress("owner"); + let receiverAddr = randomAddress("receiver"); + const sendToSelfMessage = internalMessage({ + from: ownerAddr, + body: main.transferOwnership({ newOwnerAddress: receiverAddr }), + }); + const res = await contract.sendInternalMessage(sendToSelfMessage); + expect(res.type).to.equal("success"); + expect(res.exit_code).to.equal(0); + assert(main.currentState(contract).ownerAddress?.equals(receiverAddr)); + }); + + it("should prevent others from changing owners", async () => { + main.setContractBalance(contract, 10 * main.TON()); + let ownerAddr = randomAddress("owner"); + let otherAddr = randomAddress("receiver"); + const sendToSelfMessage = internalMessage({ + from: otherAddr, + body: main.transferOwnership({ newOwnerAddress: otherAddr }), + }); + const res = await contract.sendInternalMessage(sendToSelfMessage); + expect(res.type).to.equal("failed"); + expect(res.exit_code).to.equal(401); + assert(main.currentState(contract).ownerAddress?.equals(ownerAddr)); + }); + // it("should allow the owner to change owners", async () => { // const send = await contract.sendInternalMessage( // internalMessage({